blob: c8724912df2113468a2c8d2956229ed04501f99e [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_AUDIO_METADATA_H
#define ANDROID_AUDIO_METADATA_H
#ifdef __cplusplus
#include <any>
#include <map>
#include <string>
#include <tuple>
#include <vector>
/**
* Audio Metadata: a C++ Object based map.
*
* Data is a map of strings to Datum Objects.
*
* Datum is a C++ "Object", a direct instance of std::any, but limited
* to only the following allowed types:
*
* Native Java equivalent
* int32 (int)
* int64 (long)
* float (float)
* double (double)
* std::string (String)
* Data (std::map<std::string, Datum>) (Map<String, Object>)
*
* Metadata code supports advanced automatic parceling.
* TEST ONLY:
* std::vector<Datum> (Object[]) --> vector of Objects
* std::pair<Datum, Datum> (Pair<Object, Object>) --> pair of Objects
* std::vector<std::vector<std::pair<std::string, short>>> --> recursive containers
* struct { int i0; std::vector<int> v1; std::pair<int, int> p2; } --> struct parceling
*
* The Data map accepts typed Keys, which designate the type T of the
* value associated with the Key<T> in the template parameter.
*
* CKey<T> is the constexpr version suitable for fixed compile-time constants.
* Key<T> is the non-constexpr version.
*
* Notes: for future extensibility:
*
* In order to add a new type in.
*
* 1) Add the new type to the END of the metadata_types lists below.
*
* 2) The new type can be a primitive, or make use of containers std::map, std::vector,
* std::pair, or be simple structs (see below).
*
* 3) Simple structs contain no pointers and all public data. The members can be based
* on existing types.
* a) If trivially copyable (packed) primitive data,
* add to primitive_metadata_types.
* b) If the struct requires member-wise parceling, add to structural_metadata_types
* (current limit is 4 members).
*
* 4) The type system is recursive.
*
* Design notes:
* 1) Tuple is intentionally not implemented as it isn't that readable. This can
* be revisited if the need comes up. If you have more than a couple of elements,
* we suggest embedding in a Data typed map or a Simple struct.
*
* 2) Each custom type e.g. vector<int>, pair<short, char> takes one
* slot in the type index. A full type description language is not implemented
* here for brevity and clarity.
*/
namespace android::audio_utils::metadata {
// Determine if a type is a specialization of a templated type
// Example: is_specialization_v<T, std::vector>
// https://stackoverflow.com/questions/16337610/how-to-know-if-a-type-is-a-specialization-of-stdvector
template <typename Test, template <typename...> class Ref>
struct is_specialization : std::false_type {};
template <template <typename...> class Ref, typename... Args>
struct is_specialization<Ref<Args...>, Ref>: std::true_type {};
template <typename Test, template <typename...> class Ref>
inline constexpr bool is_specialization_v = is_specialization<Test, Ref>::value;
// For static assert(false) we need a template version to avoid early failure.
// See: https://stackoverflow.com/questions/51523965/template-dependent-false
template <typename T>
inline constexpr bool dependent_false_v = false;
// Determine the number of arguments required for structured binding.
// See the discussion here and follow the links:
// https://isocpp.org/blog/2016/08/cpp17-structured-bindings-convert-struct-to-a-tuple-simple-reflection
struct any_type {
template<class T>
constexpr operator T(); // non explicit
};
template <typename T, typename... TArgs>
decltype(void(T{std::declval<TArgs>()...}), std::true_type{}) test_is_braces_constructible(int);
template <typename, typename...>
std::false_type test_is_braces_constructible(...);
template <typename T, typename... TArgs>
using is_braces_constructible = decltype(test_is_braces_constructible<T, TArgs...>(0));
// Set up type comparison system
// see std::variant for the how the type_index() may be used.
/*
* returns the index of type T in the type parameter list Ts.
*/
template <typename T, typename... Ts>
inline constexpr ssize_t type_index() {
constexpr bool checks[] = {std::is_same_v<std::decay_t<T>, std::decay_t<Ts>>...};
for (size_t i = 0; i < sizeof...(Ts); ++i) {
if (checks[i]) return i; // the index in Ts.
}
return -1; // none found.
}
// compound_type is a holder of types. There are concatenation tricks of type lists
// but we don't need them here.
template <typename... Ts>
struct compound_type {
inline static constexpr size_t size_v = sizeof...(Ts);
template <typename T>
inline static constexpr bool contains_v = type_index<T, Ts...>() >= 0;
template <typename T>
inline static constexpr ssize_t index_of() { return type_index<T, Ts...>(); }
// create a tupe equivalent of the compound type. This is useful for
// finding the nth type by std::tuple_element
using tuple_t = std::tuple<Ts...>;
/**
* Applies function f to a datum pointer a.
*
* \param f is the function to apply. It should take one argument,
* which is a (typed) pointer to the value stored in a.
* \param a is the Datum object (derived from std::any).
* \param result if non-null stores the return value of f (if f has a return value).
* result may be nullptr if one does not care about the return value of f.
* \return true on success, false if there is no applicable data stored in a.
*/
template <typename F, typename A>
static bool apply(F f, A *a, std::any *result = nullptr) {
return apply_impl<F, A, Ts...>(f, a, result);
}
// helper
// Linear search in the number of types because of non-cached std:any_cast
// lookup. See std::visit for std::variant for constant time implementation.
template <typename F, typename A, typename T, typename... Ts2>
static bool apply_impl(F f, A *a, std::any *result) {
auto t = std::any_cast<T>(a); // may be const ptr or not.
if (t == nullptr) {
return apply_impl<F, A, Ts2...>(f, a, result);
}
// only save result if the function has a non-void return type.
// and result is not nullptr.
using result_type = std::invoke_result_t<F, T*>;
if constexpr (!std::is_same_v<result_type, void>) {
if (result != nullptr) {
*result = (result_type)f(t);
return true;
}
}
f(t); // discard the result
return true;
}
// helper base class
template <typename F, typename A>
static bool apply_impl(F f __unused, A *a __unused, std::any *result __unused) {
return false;
}
};
#ifdef METADATA_TESTING
// This is a helper struct to verify that we are moving Datums instead
// of copying them.
struct MoveCount {
int32_t mMoveCount = 0;
int32_t mCopyCount = 0;
MoveCount() = default;
MoveCount(MoveCount&& other) {
mMoveCount = other.mMoveCount + 1;
mCopyCount = other.mCopyCount;
}
MoveCount(const MoveCount& other) {
mMoveCount = other.mMoveCount;
mCopyCount = other.mCopyCount + 1;
}
MoveCount &operator=(MoveCount&& other) {
mMoveCount = other.mMoveCount + 1;
mCopyCount = other.mCopyCount;
return *this;
}
MoveCount &operator=(const MoveCount& other) {
mMoveCount = other.mMoveCount;
mCopyCount = other.mCopyCount + 1;
return *this;
}
};
// We can automatically parcel this "Arbitrary" struct
// since it has no pointers and all public members.
struct Arbitrary {
int i0;
std::vector<int> v1;
std::pair<int, int> p2;
};
#endif
class Data;
class Datum;
// The order of this list must be maintained for binary compatibility
using metadata_types = compound_type<
int32_t,
int64_t,
float,
double,
std::string,
Data /* std::map<std::string, Datum> */
// OK to add at end.
#ifdef METADATA_TESTING
, std::vector<Datum> // another complex object for testing
, std::pair<Datum, Datum> // another complex object for testing
, std::vector<std::vector<std::pair<std::string, short>>> // complex object
, MoveCount
, Arbitrary
#endif
>;
// A subset of the metadata types may be directly copied as bytes
using primitive_metadata_types = compound_type<int32_t, int64_t, float, double
#ifdef METADATA_TESTING
, MoveCount
#endif
>;
// A subset of metadata types which are a struct-based.
using structural_metadata_types = compound_type<
#ifdef METADATA_TESTING
Arbitrary
#endif
>;
template <typename T>
inline constexpr bool is_primitive_metadata_type_v =
primitive_metadata_types::contains_v<T>;
template <typename T>
inline constexpr bool is_structural_metadata_type_v =
structural_metadata_types::contains_v<T>;
template <typename T>
inline constexpr bool is_metadata_type_v =
metadata_types::contains_v<T>;
/**
* Datum is the C++ version of Object, based on std::any
* to be portable to other Data Object systems on std::any. For C++, there
* are two forms of generalized Objects, std::variant and std::any.
*
* What is a variant?
* std::variant is like a std::pair<type_index, union>, where the types
* are kept in the template parameter list, and you only need to store
* the type_index of the current value's type in the template parameter
* list to find the value's type (to access data in the union).
*
* What is an any?
* std::any is a std::pair<type_func, pointer> (though the standard encourages
* small buffer optimization of the pointer for small data types,
* so the pointer might actually be data). The type_func is cleverly
* implemented templated, so that one type_func exists per type.
*
* For datum, we use std::any, which is different than mediametrics::Item
* (which uses std::variant).
*
* std::any is the C++ version of Java's Object. One benefit of std::any
* over std::variant is that it is portable outside of this package as a
* std::any, to another C++ Object system based on std::any
* (as we any_cast to discover the type). std::variant does not have this
* portability (without copy conversion) because it requires an explicit
* type list to be known in the template, so you can't exchange them freely
* as the union size and the type/type ordering will be different in general
* between two variant-based Object systems.
*
* std::any may work better with some recursive types than variants,
* as it uses pointers so that physical size need not be known for type
* definition.
*
* This is a design choice: mediametrics::Item as a closed system,
* metadata::Datum as an open system.
*
* CAUTION:
* For efficiency, prefer the use of std::any_cast<T>(std::any *)
* which returns a pointer to T (no extra copies.)
*
* std::any_cast<T>(std::any) returns an instance of T (copy constructor).
* std::get<N>(std::variant) returns a reference (no extra copies).
*
* The Data map operations are optimized to return references to
* avoid unnecessary copies.
*/
class Datum : public std::any {
public:
// Don't add any virtual functions or non-static member variables.
Datum() = default;
// Do not make these explicit
// Force type of std::any to exactly the values we permit to be parceled.
template <typename T, typename = std::enable_if_t<is_metadata_type_v<T>>>
Datum(T && t) : std::any(std::forward<T>(t)) {};
template <typename T, typename = std::enable_if_t<is_metadata_type_v<T>>>
Datum& operator=(T&& t) {
static_cast<std::any *>(this)->operator=(std::forward<T>(t));
return *this;
}
Datum(const char *t) : std::any(std::string(t)) {}; // special string handling
};
// PREVENT INCORRECT MODIFICATIONS
// Datum is a helping wrapper on std::any
// Don't add any non-static members
static_assert(sizeof(Datum) == sizeof(std::any));
// Nothing is virtual
static_assert(!std::is_polymorphic_v<Datum>);
/**
* Keys
*
* Audio Metadata keys are typed. Similar to variant's template typenames,
* which directly indicate possible types in the union, the Audio Metadata
* Keys contain the Value's Type in the Key's template type parameter.
*
* Example:
*
* inline constexpr CKey<int64_t> MY_BIGINT("bigint_is_mine");
* inline constexpr CKey<Data> TABLE("table");
*
* Thus if we have a Data object d:
*
* decltype(d[TABLE]) is Data
* decltype(d[MY_BIGINT) is int64_t
*/
/**
* Key is a non-constexpr key which has local storage in a string.
*/
template <typename T, typename = std::enable_if_t<is_metadata_type_v<T>>>
class Key : private std::string {
public:
using std::string::string; // base constructor
const char *getName() const { return c_str(); }
};
/**
* CKey is a constexpr key, which is preferred.
*
* inline constexpr CKey<int64_t> MY_BIGINT("bigint_is_mine");
*/
template <typename T, typename = std::enable_if_t<is_metadata_type_v<T>>>
class CKey {
const char * const mName;
public:
explicit constexpr CKey(const char *name) : mName(name) {}
CKey(const Key<T> &key) : mName(key.getName()) {}
const char *getName() const { return mName; }
};
/**
* Data is the storage for our Datums.
*
* It is implemented on top of std::map<std::string, Datum>
* but we augment it with typed Key
* getters and setters, as well as operator[] overloads.
*/
class Data : public std::map<std::string, Datum> {
public:
// Don't add any virtual functions or non-static member variables.
// We supplement the raw form of map with
// the following typed form using Key.
// Intentionally there is no get(), we suggest *get_ptr()
template <template <typename /* T */, typename... /* enable-ifs */> class K, typename T>
T* get_ptr(const K<T>& key, bool allocate = false) {
auto it = find(key.getName());
if (it == this->end()) {
if (!allocate) return nullptr;
it = emplace(key.getName(), T{}).first;
}
return std::any_cast<T>(&it->second);
}
template <template <typename, typename...> class K, typename T>
const T* get_ptr(const K<T>& key) const {
auto it = find(key.getName());
if (it == this->end()) return nullptr;
return std::any_cast<T>(&it->second);
}
template <template <typename, typename...> class K, typename T>
void put(const K<T>& key, T && t) {
(*this)[key.getName()] = std::forward<T>(t);
}
template <template <typename, typename...> class K>
void put(const K<std::string>& key, const char *value) {
(*this)[key.getName()] = value;
}
// We overload our operator[] so we unhide the one in the base class.
using std::map<std::string, Datum>::operator[];
template <template <typename, typename...> class K, typename T>
T& operator[](const K<T> &key) {
return *get_ptr(key, /* allocate */ true);
}
template <template <typename, typename...> class K, typename T>
const T& operator[](const K<T> &key) const {
return *get_ptr(key);
}
};
// PREVENT INCORRECT MODIFICATIONS
// Data is a helping wrapper on std::map
// Don't add any non-static members
static_assert(sizeof(Data) == sizeof(std::map<std::string, Datum>));
// Nothing is virtual
static_assert(!std::is_polymorphic_v<Data>);
/**
* Parceling of Datum by recursive descent to a ByteString
*
* Parceling Format:
* All values are native endian order.
*
* Datum = {
* (type_size_t) Type (the type index from type_as_value<T>.)
* (datum_size_t) Size (size of Payload)
* (byte string) Payload<Type>
* }
*
* Payload<Primitive_Type> = { bytes in native endian order }
*
* Payload<String> = { (index_size_t) number of elements (not including zero termination)
* bytes of string data.
* }
*
* Vector, Map, Container types:
* Payload<Type> = { (index_size_t) number of elements
* (byte string) Payload<Element_Type> * number
* }
*
* Pair container types:
* Payload<Type> = { (byte string) Payload<first>,
* (byte string) Payload<second>
* }
*
* Note: Data is a std::map<std::string, Datum>
*
* Design notes:
*
* 1) The size of each datum allows skipping of unknown types for compatibility
* of older code with newer Datums.
*
* Examples:
* Payload<Int32> of 123
* [ value of 123 ] = 0x7b 0x00 0x00 0x00 123
*
* Example of Payload<String> of std::string("hi"):
* [ (index_size_t) length ] = 0x02 0x00 0x00 0x00 2 strlen("hi")
* [ raw bytes "hi" ] = 0x68 0x69 "hi"
*
* Payload<Data>
* [ (index_size_t) entries ]
* [ raw bytes (entry 1) Key (Payload<String>)
* Value (Datum)
* ... (until #entries) ]
*
* Example of Payload<Data> of {{"hello", "world"},
* {"value", (int32_t)1000}};
* [ (index_size_t) #entries ] = 0x02 0x00 0x00 0x00 2 entries
* Key (Payload<String>)
* [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("hello")
* [ raw bytes "hello" ] = 0x68 0x65 0x6c 0x6c 0x6f "hello"
* Value (Datum)
* [ (type_size_t) type ] = 0x05 0x00 0x00 0x00 5 (TYPE_STRING)
* [ (datum_size_t) size ] = 0x09 0x00 0x00 0x00 sizeof(index_size_t) +
* strlen("world")
* Payload<String>
* [ (index_size_t) length ] = 0x05 0x00 0x00 0x00 5 strlen("world")
* [ raw bytes "world" ] = 0x77 0x6f 0x72 0x6c 0x64 "world"
* Key (Payload<String>)
* [ index_size_t length ] = 0x05 0x00 0x00 0x00 5 strlen("value")
* [ raw bytes "value" ] = 0x76 0x61 0x6c 0x75 0x65 "value"
* Value (Datum)
* [ (type_size_t) type ] = 0x01 0x00 0x00 0x00 1 (TYPE_INT32)
* [ (datum_size_t) size ] = 0x04 0x00 0x00 0x00 4 sizeof(int32_t)
* Payload<Int32>
* [ raw bytes 1000 ] = 0xe8 0x03 0x00 0x00 1000
*
* Metadata is passed as a Payload<Data>.
* An implementation dependent detail is that the Keys are always
* stored sorted, so the byte string representation generated is unique.
*/
// Platform Apex compatibility note:
// type_size_t may not change.
using type_size_t = uint32_t;
// Platform Apex compatibility note:
// index_size_t must not change.
using index_size_t = uint32_t;
// Platform Apex compatibility note:
// datum_size_t must not change.
using datum_size_t = uint32_t;
// The particular implementation of ByteString may change
// without affecting compatibility.
using ByteString = std::basic_string<uint8_t>;
/*
These should correspond to the Java AudioMetadata.java
Permitted type indexes:
TYPE_NONE = 0,
TYPE_INT32 = 1,
TYPE_INT64 = 2,
TYPE_FLOAT = 3,
TYPE_DOUBLE = 4,
TYPE_STRING = 5,
TYPE_DATA = 6,
*/
template <typename T>
inline constexpr type_size_t get_type_as_value() {
return (type_size_t)(metadata_types::index_of<T>() + 1);
}
template <typename T>
inline constexpr type_size_t type_as_value = get_type_as_value<T>();
// forward decl for recursion - do not remove.
bool copyToByteString(const Datum& datum, ByteString &bs);
template <template <typename ...> class V, typename... Args>
bool copyToByteString(const V<Args...>& v, ByteString&bs);
// end forward decl
// primitives handled here
template <typename T>
std::enable_if_t<
is_primitive_metadata_type_v<T> || std::is_arithmetic_v<std::decay_t<T>>,
bool
>
copyToByteString(const T& t, ByteString& bs) {
bs.append((uint8_t*)&t, sizeof(t));
return true;
}
// pairs handled here
template <typename A, typename B>
bool copyToByteString(const std::pair<A, B>& p, ByteString& bs) {
return copyToByteString(p.first, bs) && copyToByteString(p.second, bs);
}
// containers
template <template <typename ...> class V, typename... Args>
bool copyToByteString(const V<Args...>& v, ByteString& bs) {
if (v.size() > std::numeric_limits<index_size_t>::max()) return false;
index_size_t size = v.size();
if (!copyToByteString(size, bs)) return false;
if constexpr (std::is_same_v<std::decay_t<V<Args...>>, std::string>) {
bs.append((uint8_t*)v.c_str());
} else /* constexpr */ {
for (const auto &d : v) { // handles std::vector and std::map
if (!copyToByteString(d, bs)) return false;
}
}
return true;
}
// simple struct data (use structured binding to extract members)
template <typename T>
std::enable_if_t<
is_structural_metadata_type_v<T>,
bool
>
copyToByteString(const T& t, ByteString& bs) {
using type = std::decay_t<T>;
if constexpr (is_braces_constructible<type, any_type, any_type, any_type, any_type>{}) {
const auto& [e1, e2, e3, e4] = t;
return copyToByteString(e1, bs)
&& copyToByteString(e2, bs)
&& copyToByteString(e3, bs)
&& copyToByteString(e4, bs);
} else if constexpr (is_braces_constructible<type, any_type, any_type, any_type>{}) {
const auto& [e1, e2, e3] = t;
return copyToByteString(e1, bs)
&& copyToByteString(e2, bs)
&& copyToByteString(e3, bs);
} else if constexpr (is_braces_constructible<type, any_type, any_type>{}) {
const auto& [e1, e2] = t;
return copyToByteString(e1, bs)
&& copyToByteString(e2, bs);
} else if constexpr(is_braces_constructible<type, any_type>{}) {
const auto& [e1] = t;
return copyToByteString(e1, bs);
} else if constexpr (is_braces_constructible<type>{}) {
return true; // like std::monostate - no members
} else /* constexpr */ {
static_assert(dependent_false_v<T>);
}
}
// Datum
bool copyToByteString(const Datum& datum, ByteString &bs) {
bool success = false;
return metadata_types::apply([&bs, &success](auto ptr) {
// save type
const type_size_t type = type_as_value<decltype(*ptr)>;
if (!copyToByteString(type, bs)) return;
// get current location
const size_t idx = bs.size();
// save size (replaced later)
datum_size_t datum_size = 0;
if (!copyToByteString(datum_size, bs)) return;
// copy data
if (!copyToByteString(*ptr, bs)) return;
// save correct size
const size_t diff = bs.size() - idx - sizeof(datum_size);
if (diff > std::numeric_limits<datum_size_t>::max()) return;
datum_size = diff;
bs.replace(idx, sizeof(datum_size), (uint8_t*)&datum_size, sizeof(datum_size));
success = true;
}, &datum) && success;
}
/**
* Obtaining the Datum back from ByteString
*/
// A container that lists all the unknown types found during parsing.
using ByteStringUnknowns = std::vector<type_size_t>;
// forward decl for recursion - do not remove.
bool copyFromByteString(Datum *datum, const ByteString &bs, size_t& idx,
ByteStringUnknowns *unknowns);
template <template <typename ...> class V, typename... Args>
bool copyFromByteString(V<Args...> *v, const ByteString& bs, size_t& idx,
ByteStringUnknowns *unknowns);
// primitive
template <typename T>
std::enable_if_t<
is_primitive_metadata_type_v<T> ||
std::is_arithmetic_v<std::decay_t<T>>,
bool
>
copyFromByteString(T *dest, const ByteString& bs, size_t& idx,
ByteStringUnknowns *unknowns __unused) {
if (idx + sizeof(T) > bs.size()) return false;
bs.copy((uint8_t*)dest, sizeof(T), idx);
idx += sizeof(T);
return true;
}
// pairs
template <typename A, typename B>
bool copyFromByteString(std::pair<A, B>* p, const ByteString& bs, size_t& idx,
ByteStringUnknowns *unknowns) {
return copyFromByteString(&p->first, bs, idx, unknowns)
&& copyFromByteString(&p->second, bs, idx, unknowns);
}
// containers
template <template <typename ...> class V, typename... Args>
bool copyFromByteString(V<Args...> *v, const ByteString& bs, size_t& idx,
ByteStringUnknowns *unknowns) {
index_size_t size;
if (!copyFromByteString(&size, bs, idx, unknowns)) return false;
if constexpr (std::is_same_v<std::decay_t<V<Args...>>, std::string>) {
if (size > bs.size() - idx) return false;
v->resize(size);
for (index_size_t i = 0; i < size; ++i) {
(*v)[i] = bs[idx++];
}
} else if constexpr (is_specialization_v<std::decay_t<V<Args...>>, std::vector>) {
for (index_size_t i = 0; i < size; ++i) {
std::decay_t<decltype(*v->begin())> value{};
if (!copyFromByteString(&value, bs, idx, unknowns)) {
return false;
}
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Datum>) {
if (!value.has_value()) {
continue; // ignore empty datum values in a vector.
}
}
v->emplace_back(std::move(value));
}
} else if constexpr (is_specialization_v<std::decay_t<V<Args...>>, std::map>) {
for (index_size_t i = 0; i < size; ++i) {
// we can't directly use pair because there may be internal const decls.
std::decay_t<decltype(v->begin()->first)> key{};
std::decay_t<decltype(v->begin()->second)> value{};
if (!copyFromByteString(&key, bs, idx, unknowns) ||
!copyFromByteString(&value, bs, idx, unknowns)) {
return false;
}
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Datum>) {
if (!value.has_value()) {
continue; // ignore empty datum values in a map.
}
}
v->emplace(std::move(key), std::move(value));
}
} else /* constexpr */ {
for (index_size_t i = 0; i < size; ++i) {
std::decay_t<decltype(*v->begin())> value{};
if (!copyFromByteString(&value, bs, idx, unknowns)) {
return false;
}
v->emplace(std::move(value));
}
}
return true;
}
// simple structs (use structured binding to extract members)
template <typename T>
typename std::enable_if_t<is_structural_metadata_type_v<T>, bool>
copyFromByteString(T *t, const ByteString& bs, size_t& idx,
ByteStringUnknowns *unknowns) {
using type = std::decay_t<T>;
if constexpr (is_braces_constructible<type, any_type, any_type, any_type, any_type>{}) {
auto& [e1, e2, e3, e4] = *t;
return copyFromByteString(&e1, bs, idx, unknowns)
&& copyFromByteString(&e2, bs, idx, unknowns)
&& copyFromByteString(&e3, bs, idx, unknowns)
&& copyFromByteString(&e4, bs, idx, unknowns);
} else if constexpr (is_braces_constructible<type, any_type, any_type, any_type>{}) {
auto& [e1, e2, e3] = *t;
return copyFromByteString(&e1, bs, idx, unknowns)
&& copyFromByteString(&e2, bs, idx, unknowns)
&& copyFromByteString(&e3, bs, idx, unknowns);
} else if constexpr (is_braces_constructible<type, any_type, any_type>{}) {
auto& [e1, e2] = *t;
return copyFromByteString(&e1, bs, idx, unknowns)
&& copyFromByteString(&e2, bs, idx, unknowns);
} else if constexpr (is_braces_constructible<type, any_type>{}) {
auto& [e1] = *t;
return copyFromByteString(&e1, bs, idx, unknowns);
} else if constexpr (is_braces_constructible<type>{}) {
return true; // like std::monostate - no members
} else /* constexpr */ {
static_assert(dependent_false_v<T>);
}
}
namespace tedious_details {
//
// We build a function table at compile time to lookup the proper copyFromByteString method.
// See:
// https://stackoverflow.com/questions/36785345/void-to-the-nth-element-of-stdtuple-at-runtime
// Constant time implementation of std::visit (variant)
template <typename CompoundT, size_t Index>
bool copyFromByteString(Datum *datum, const ByteString &bs, size_t &idx, size_t endIdx,
ByteStringUnknowns *unknowns) {
using T = std::tuple_element_t<Index, typename CompoundT::tuple_t>;
T value;
if (!android::audio_utils::metadata::copyFromByteString(
&value, bs, idx, unknowns)) return false; // have we parsed correctly?
if (idx != endIdx) return false; // have we consumed the correct number of bytes?
*datum = std::move(value);
return true;
}
template <typename CompoundT, size_t... Indexes>
constexpr bool copyFromByteString(Datum *datum, const ByteString &bs,
size_t &idx, size_t endIdx, ByteStringUnknowns *unknowns,
size_t typeIndex, std::index_sequence<Indexes...>)
{
using function_type =
bool (*)(Datum*, const ByteString&, size_t&, size_t, ByteStringUnknowns*);
function_type constexpr ptrs[] = {
&copyFromByteString<CompoundT, Indexes>...
};
return ptrs[typeIndex](datum, bs, idx, endIdx, unknowns);
}
template <typename CompoundT>
__attribute__((noinline))
constexpr bool copyFromByteString(Datum *datum, const ByteString &bs,
size_t &idx, size_t endIdx, ByteStringUnknowns *unknowns, size_t typeIndex) {
return copyFromByteString<CompoundT>(
datum, bs, idx, endIdx, unknowns,
typeIndex, std::make_index_sequence<CompoundT::size_v>());
}
} // namespace tedious_details
bool copyFromByteString(Datum *datum, const ByteString &bs, size_t& idx,
ByteStringUnknowns *unknowns) {
type_size_t type;
if (!copyFromByteString(&type, bs, idx, unknowns)) return false;
datum_size_t datum_size;
if (!copyFromByteString(&datum_size, bs, idx, unknowns)) return false;
if (datum_size > bs.size() - idx) return false;
const size_t endIdx = idx + datum_size;
if (type == 0 || type > metadata_types::size_v) {
idx = endIdx; // skip unrecognized type.
if (unknowns != nullptr) {
unknowns->push_back(type);
return true; // allow further recursion.
}
return false;
}
// use special trick to instantiate all the types for copyFromByteString
// in a table and find the right method from table lookup.
return tedious_details::copyFromByteString<metadata_types>(
datum, bs, idx, endIdx, unknowns, type - 1);
}
// Handy helpers - these are the most efficient ways to parcel Data.
/**
* Returns the Data map from a byte string.
*
* If unknowns is nullptr, then any unknown entries during parsing will cause
* an empty map to be returned.
*
* If unknowns is non-null, then it contains all of the unknown types
* encountered during parsing, and a partial map will be returned excluding all
* unknown types encountered.
*/
Data dataFromByteString(const ByteString &bs,
ByteStringUnknowns *unknowns = nullptr) {
Data d;
size_t idx = 0;
if (!copyFromByteString(&d, bs, idx, unknowns)) {
return {};
}
return d; // copy elision
}
ByteString byteStringFromData(const Data &data) {
ByteString bs;
copyToByteString(data, bs);
return bs; // copy elision
}
} // namespace android::audio_utils::metadata
#endif // __cplusplus
// C API (see C++ API above for details)
/** \cond */
__BEGIN_DECLS
/** \endcond */
typedef struct audio_metadata_t audio_metadata_t;
/**
* \brief Creates a metadata object
*
* \return the metadata object or NULL on failure. Caller must call
* audio_metadata_destroy to free memory.
*/
audio_metadata_t *audio_metadata_create();
/**
* \brief Put key value pair where the value type is int32_t to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata or key is null.
*/
int audio_metadata_put_int32(audio_metadata_t *metadata, const char *key, int32_t value);
/**
* \brief Put key value pair where the value type is int64_t to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata or key is null.
*/
int audio_metadata_put_int64(audio_metadata_t *metadata, const char *key, int64_t value);
/**
* \brief Put key value pair where the value type is float to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata or key is null.
*/
int audio_metadata_put_float(audio_metadata_t *metadata, const char *key, float value);
/**
* \brief Put key value pair where the value type is double to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata or key is null.
*/
int audio_metadata_put_double(audio_metadata_t *metadata, const char *key, double value);
/**
* \brief Put key value pair where the value type is `const char *` to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata, key or value is null.
*/
int audio_metadata_put_string(audio_metadata_t *metadata, const char *key, const char *value);
/**
* \brief Put key value pair where the value type is audio_metadata_t to audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the element to be put.
* \param value the value of the element to be put.
* \return 0 if the key value pair is put successfully into the audio metadata.
* -EINVAL if metadata, key or value is null.
*/
int audio_metadata_put_data(audio_metadata_t *metadata, const char *key, audio_metadata_t *value);
/**
* \brief The type is not allowed in audio metadata. Only log the key and return -EINVAL here.
*/
int audio_metadata_put_unknown(audio_metadata_t *metadata, const char *key, const void *value);
// use C Generics to provide interfaces for put/get functions
// See: https://en.cppreference.com/w/c/language/generic
/**
* A generic interface to put key value pair into the audio metadata.
*/
#define audio_metadata_put(metadata, key, value) _Generic((value), \
int32_t: audio_metadata_put_int32, \
int64_t: audio_metadata_put_int64, \
float: audio_metadata_put_float, \
double: audio_metadata_put_double, \
const char*: audio_metadata_put_string, \
audio_metadata_t*: audio_metadata_put_data, \
default: audio_metadata_put_unknown \
)(metadata, key, value)
/**
* \brief Get mapped value whose type is int32_t by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not int32_t.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_int32(audio_metadata_t *metadata, const char *key, int32_t *value);
/**
* \brief Get mapped value whose type is int64_t by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not int32_t.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_int64(audio_metadata_t *metadata, const char *key, int64_t *value);
/**
* \brief Get mapped value whose type is float by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not float.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_float(audio_metadata_t *metadata, const char *key, float *value);
/**
* \brief Get mapped value whose type is double by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not double.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_double(audio_metadata_t *metadata, const char *key, double *value);
/**
* \brief Get mapped value whose type is std::string by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written. The memory will be allocated in the
* function, which must be freed by caller.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not std::string.
* -ENOMEM when fails allocating memory for value.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_string(audio_metadata_t *metadata, const char *key, char **value);
/**
* \brief Get mapped value whose type is audio_metadata_t by a given key from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key value to get value.
* \param value the mapped value to be written. The memory will be allocated in the
* function, which should be free by caller via audio_metadata_destroy.
* \return -EINVAL when 1) metadata is null, 2) key is null, or 3) value is null.
* -ENOENT when 1) key is found in the audio metadata,
* 2) the type of mapped value is not audio_utils::metadata::Data.
* -ENOMEM when fails allocating memory for value.
* 0 if successfully find the mapped value.
*/
int audio_metadata_get_data(audio_metadata_t *metadata, const char *key, audio_metadata_t **value);
/**
* \brief The data type is not allowed in audio metadata. Only log the key and return -EINVAL here.
*/
int audio_metadata_get_unknown(audio_metadata_t *metadata, const char *key, void *value);
/**
* A generic interface to get mapped value by a given key from audio metadata. The value object
* will remain the same if the key is not found in the audio metadata.
*/
#define audio_metadata_get(metadata, key, value) _Generic((value), \
int32_t*: audio_metadata_get_int32, \
int64_t*: audio_metadata_get_int64, \
float*: audio_metadata_get_float, \
double*: audio_metadata_get_double, \
char**: audio_metadata_get_string, \
audio_metadata_t**: audio_metadata_get_data, \
default: audio_metadata_get_unknown \
)(metadata, key, value)
/**
* \brief Remove item from audio metadata.
*
* \param metadata the audio metadata object.
* \param key the key of the item that is going to be removed.
* \return -EINVAL if metadata or key is null. Otherwise, return the number of elements erased.
*/
ssize_t audio_metadata_erase(audio_metadata_t *metadata, const char *key);
/**
* \brief Destroys the metadata object
*
* \param metadata object returned by create, if NULL nothing happens.
*/
void audio_metadata_destroy(audio_metadata_t *metadata);
/**
* \brief Unpack byte string into a given audio metadata
*
* \param byteString a byte string that contains data to convert to audio metadata.
* \param length the length of the byte string
* \return the audio metadata object that contains the converted data. Caller must call
* audio_metadata_destroy to free the memory.
*/
audio_metadata_t *audio_metadata_from_byte_string(const uint8_t *byteString, size_t length);
/**
* \brief Pack the audio metadata into a byte string
*
* \param metadata the audio metadata object to be converted.
* \param byteString the buffer to write data to. The memory will be allocated
* in the function, which must be freed by caller via free().
* \return -EINVAL if metadata or byteString is null.
* -ENOMEM if fails to allocate memory for byte string.
* The length of the byte string.
*/
ssize_t byte_string_from_audio_metadata(audio_metadata_t *metadata, uint8_t **byteString);
/** \cond */
__END_DECLS
/** \endcond */
#endif // !ANDROID_AUDIO_METADATA_H