浏览代码

update cspot

philippe44 2 年之前
父节点
当前提交
7e5f27af12
共有 100 个文件被更改,包括 4889 次插入716 次删除
  1. 0 1
      components/spotify/CMakeLists.txt
  2. 7 6
      components/spotify/Shim.cpp
  3. 1 2
      components/spotify/Shim.h
  4. 17 13
      components/spotify/cspot/CMakeLists.txt
  5. 二进制
      components/spotify/cspot/bell/.DS_Store
  6. 126 0
      components/spotify/cspot/bell/.gitignore
  7. 3 0
      components/spotify/cspot/bell/.gitmodules
  8. 192 59
      components/spotify/cspot/bell/CMakeLists.txt
  9. 23 10
      components/spotify/cspot/bell/cJSON/CMakeLists.txt
  10. 16 7
      components/spotify/cspot/bell/cJSON/cJSON.c
  11. 7 0
      components/spotify/cspot/bell/cJSON/cJSON.h
  12. 78 17
      components/spotify/cspot/bell/cJSON/tests/misc_tests.c
  13. 53 5
      components/spotify/cspot/bell/include/BaseHTTPServer.h
  14. 10 5
      components/spotify/cspot/bell/include/BellLogger.h
  15. 17 1
      components/spotify/cspot/bell/include/BellTask.h
  16. 3 0
      components/spotify/cspot/bell/include/BellUtils.h
  17. 127 0
      components/spotify/cspot/bell/include/BufferedStream.h
  18. 71 7
      components/spotify/cspot/bell/include/Crypto.h
  19. 0 78
      components/spotify/cspot/bell/include/CryptoMbedTLS.h
  20. 0 84
      components/spotify/cspot/bell/include/CryptoOpenSSL.h
  21. 1 1
      components/spotify/cspot/bell/include/HTTPClient.h
  22. 11 6
      components/spotify/cspot/bell/include/HTTPServer.h
  23. 1 1
      components/spotify/cspot/bell/include/HTTPStream.h
  24. 20 9
      components/spotify/cspot/bell/include/TCPSocket.h
  25. 7 21
      components/spotify/cspot/bell/include/TLSSocket.h
  26. 8 0
      components/spotify/cspot/bell/include/TimeDefs.h
  27. 19 0
      components/spotify/cspot/bell/include/audio/codec/AACDecoder.h
  28. 18 0
      components/spotify/cspot/bell/include/audio/codec/ALACDecoder.h
  29. 23 0
      components/spotify/cspot/bell/include/audio/codec/AudioCodecs.h
  30. 39 0
      components/spotify/cspot/bell/include/audio/codec/BaseCodec.h
  31. 1 1
      components/spotify/cspot/bell/include/audio/codec/DecoderGlobals.h
  32. 19 0
      components/spotify/cspot/bell/include/audio/codec/MP3Decoder.h
  33. 19 0
      components/spotify/cspot/bell/include/audio/codec/OPUSDecoder.h
  34. 25 0
      components/spotify/cspot/bell/include/audio/codec/VorbisDecoder.h
  35. 10 0
      components/spotify/cspot/bell/include/audio/container/AudioContainers.h
  36. 104 0
      components/spotify/cspot/bell/include/audio/container/BaseContainer.h
  37. 108 0
      components/spotify/cspot/bell/include/audio/container/Mpeg4Atoms.h
  38. 114 0
      components/spotify/cspot/bell/include/audio/container/Mpeg4Container.h
  39. 206 0
      components/spotify/cspot/bell/include/audio/container/Mpeg4Types.h
  40. 81 0
      components/spotify/cspot/bell/include/audio/container/WebmContainer.h
  41. 713 0
      components/spotify/cspot/bell/include/audio/container/WebmElements.h
  42. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/AC101AudioSink.h
  43. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/BufferedAudioSink.h
  44. 27 0
      components/spotify/cspot/bell/include/audio/sinks/esp/ES8311AudioSink.h
  45. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/ES8388AudioSink.h
  46. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/ES9018AudioSink.h
  47. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/InternalAudioSink.h
  48. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/PCM5102AudioSink.h
  49. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/SPDIFAudioSink.h
  50. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/TAS5711AudioSink.h
  51. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/ac101.h
  52. 0 0
      components/spotify/cspot/bell/include/audio/sinks/esp/adac.h
  53. 121 0
      components/spotify/cspot/bell/include/audio/sinks/esp/es8311.h
  54. 166 0
      components/spotify/cspot/bell/include/audio/sinks/esp/esxxx_common.h
  55. 1 1
      components/spotify/cspot/bell/include/audio/sinks/unix/ALSAAudioSink.h
  56. 0 0
      components/spotify/cspot/bell/include/audio/sinks/unix/NamedPipeAudioSink.h
  57. 0 0
      components/spotify/cspot/bell/include/audio/sinks/unix/PortAudioSink.h
  58. 18 0
      components/spotify/cspot/bell/include/platform/MDNSService.h
  59. 4 0
      components/spotify/cspot/bell/include/platform/WrappedSemaphore.h
  60. 15 0
      components/spotify/cspot/bell/include/platform/win32/win32shim.h
  61. 39 0
      components/spotify/cspot/bell/nanopb/.github/workflows/cifuzz.yml
  62. 63 0
      components/spotify/cspot/bell/nanopb/.github/workflows/platformio.yaml
  63. 15 0
      components/spotify/cspot/bell/nanopb/.github/workflows/spm.yml
  64. 16 0
      components/spotify/cspot/bell/nanopb/AUTHORS.txt
  65. 47 0
      components/spotify/cspot/bell/nanopb/CHANGELOG.txt
  66. 5 5
      components/spotify/cspot/bell/nanopb/CMakeLists.txt
  67. 0 3
      components/spotify/cspot/bell/nanopb/examples/cmake_relpath/CMakeLists.txt
  68. 0 3
      components/spotify/cspot/bell/nanopb/examples/cmake_simple/CMakeLists.txt
  69. 24 5
      components/spotify/cspot/bell/nanopb/pb.h
  70. 16 13
      components/spotify/cspot/bell/nanopb/pb_decode.c
  71. 3 4
      components/spotify/cspot/bell/nanopb/pb_encode.c
  72. 1 1
      components/spotify/cspot/bell/nanopb/tests/site_scons/platforms/avr/run_test.c
  73. 二进制
      components/spotify/cspot/bell/src/.DS_Store
  74. 0 88
      components/spotify/cspot/bell/src/BaseHTTPServer.cpp0
  75. 2 2
      components/spotify/cspot/bell/src/BellLogger.cpp
  76. 2 2
      components/spotify/cspot/bell/src/BinaryReader.cpp
  77. 174 0
      components/spotify/cspot/bell/src/BufferedStream.cpp
  78. 1 3
      components/spotify/cspot/bell/src/Crypto.cpp
  79. 0 184
      components/spotify/cspot/bell/src/CryptoOpenSSL.cpp
  80. 0 8
      components/spotify/cspot/bell/src/DecoderGlobals.cpp
  81. 88 58
      components/spotify/cspot/bell/src/HTTPServer.cpp
  82. 7 1
      components/spotify/cspot/bell/src/HTTPStream.cpp
  83. 1 1
      components/spotify/cspot/bell/src/TLSSocket.cpp
  84. 37 0
      components/spotify/cspot/bell/src/audio/codec/AACDecoder.cpp
  85. 37 0
      components/spotify/cspot/bell/src/audio/codec/ALACDecoder.cpp
  86. 71 0
      components/spotify/cspot/bell/src/audio/codec/AudioCodecs.cpp
  87. 13 0
      components/spotify/cspot/bell/src/audio/codec/BaseCodec.cpp
  88. 8 0
      components/spotify/cspot/bell/src/audio/codec/DecoderGlobals.cpp
  89. 35 0
      components/spotify/cspot/bell/src/audio/codec/MP3Decoder.cpp
  90. 46 0
      components/spotify/cspot/bell/src/audio/codec/OPUSDecoder.cpp
  91. 120 0
      components/spotify/cspot/bell/src/audio/codec/VorbisDecoder.cpp
  92. 18 0
      components/spotify/cspot/bell/src/audio/container/AudioContainers.cpp
  93. 78 0
      components/spotify/cspot/bell/src/audio/container/BaseContainer.cpp
  94. 359 0
      components/spotify/cspot/bell/src/audio/container/Mpeg4Container.cpp
  95. 171 0
      components/spotify/cspot/bell/src/audio/container/Mpeg4Parser.cpp
  96. 162 0
      components/spotify/cspot/bell/src/audio/container/Mpeg4ParserFrag.cpp
  97. 215 0
      components/spotify/cspot/bell/src/audio/container/Mpeg4Utils.cpp
  98. 172 0
      components/spotify/cspot/bell/src/audio/container/WebmContainer.cpp
  99. 126 0
      components/spotify/cspot/bell/src/audio/container/WebmParser.cpp
  100. 67 0
      components/spotify/cspot/bell/src/audio/container/WebmUtils.cpp

+ 0 - 1
components/spotify/CMakeLists.txt

@@ -8,7 +8,6 @@ idf_component_register(
 		LDFRAGMENTS "linker.lf"
 )
 
-add_definitions(-DBELL_USE_MBEDTLS)
 add_definitions(-Wno-unused-variable -Wno-unused-const-variable -Wchar-subscripts -Wunused-label -Wmaybe-uninitialized -Wmisleading-indentation)
 
 set(BELL_DISABLE_CODECS ON)

+ 7 - 6
components/spotify/Shim.cpp

@@ -116,6 +116,7 @@ static void cspotTask(void *pvParameters) {
             spircController = std::make_shared<SpircController>(mercuryManager, cspot.blob->username, audioSink);
 
 			spircController->setEventHandler([](CSpotEvent &event) {
+ESP_LOGI(TAG, "Getting Spotify event %d ", (int) event.eventType);				
             switch (event.eventType) {
             case CSpotEventType::TRACK_INFO: {
                 TrackInfo track = std::get<TrackInfo>(event.data);
@@ -293,18 +294,18 @@ bool NVSFile::flush() {
  * Shim HTTP server for spirc
  */
 static esp_err_t handlerWrapper(httpd_req_t *req) {
-	bell::HTTPRequest request = { };
+	std::unique_ptr<bell::HTTPRequest> request = std::make_unique<bell::HTTPRequest>();
 	char *query = NULL, *body = NULL;
 	bell::httpHandler *handler = (bell::httpHandler*) req->user_ctx;
 	size_t query_len = httpd_req_get_url_query_len(req);
 
-	request.connection = httpd_req_to_sockfd(req);
+	request->connection = httpd_req_to_sockfd(req);
 
 	// get body if any (add '\0' at the end if used as string)
 	if (req->content_len) {
 		body = (char*) calloc(1, req->content_len + 1);
 		int size = httpd_req_recv(req, body, req->content_len);
-		request.body = body;
+		request->body = body;
 		ESP_LOGD(TAG,"wrapper received body %d/%d", size, req->content_len);
 	}
 
@@ -324,7 +325,7 @@ static esp_err_t handlerWrapper(httpd_req_t *req) {
 	while (key) {
 		char *value = strchr(key, '=');
 		*value++ = '\0';
-		request.queryParams[key] = value;
+		request->queryParams[key] = value;
 		ESP_LOGD(TAG,"wrapper received key:%s value:%s", key, value);
 		key = strtok(NULL, "&");
 	};
@@ -337,12 +338,12 @@ static esp_err_t handlerWrapper(httpd_req_t *req) {
 	 and then we'll return. So we can't obtain the response to be sent, as esp_http_server
 	 normally expects, instead respond() will use raw socket and close connection
 	*/
-	(*handler)(request);
+	(*handler)(std::move(request));
 
 	return ESP_OK;
 }
 
-void ShimHTTPServer::registerHandler(bell::RequestType requestType, const std::string &routeUrl, bell::httpHandler handler) {
+void ShimHTTPServer::registerHandler(bell::RequestType requestType, const std::string &routeUrl, bell::httpHandler handler, bool readDataToStr) {
 	httpd_uri_t request = { 
 		.uri = routeUrl.c_str(), 
 		.method = (requestType == bell::RequestType::GET ? HTTP_GET : HTTP_POST),

+ 1 - 2
components/spotify/Shim.h

@@ -44,7 +44,6 @@ private:
 	
 public:
    ShimHTTPServer(httpd_handle_t server, int port) { serverHandle = server; serverPort = port; }
-
-   void registerHandler(bell::RequestType requestType, const std::string &, bell::httpHandler);
+   void registerHandler(bell::RequestType requestType, const std::string &, bell::httpHandler, bool readDataToStr = false);
    void respond(const bell::HTTPResponse &);
 };

+ 17 - 13
components/spotify/cspot/CMakeLists.txt

@@ -6,11 +6,21 @@ project(cspot)
 set(CSPOT_EXTERNAL_BELL "" CACHE STRING "External bell library target name, optional")
 
 # CMake options
-set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD 20)
+
+if(MSVC)
+	add_compile_definitions(NOMINMAX _WINSOCK_DEPRECATED_NO_WARNINGS _CRT_SECURE_NO_WARNINGS)
+	add_definitions(/wd4068 /wd4244 /wd4018 /wd4101 /wd4102 /wd4142 /wd4996)
+endif()
 
 # Main library sources
 file(GLOB SOURCES "src/*.cpp" "src/*.c")
 
+if(WIN32)
+    list(APPEND SOURCES "mdnssvc/mdns.c" "mdnssvc/mdnsd.c")
+    list(APPEND EXTRA_INCLUDES "mdnssvc")
+endif()	
+
 # Use externally specified bell library or the submodule
 if(CSPOT_EXTERNAL_BELL)
     list(APPEND EXTRA_LIBS ${CSPOT_EXTERNAL_BELL})
@@ -26,21 +36,15 @@ if(UNIX AND NOT APPLE)
 endif()
 
 # Build protobuf code
-#set(NANOPB_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}")
-#file(GLOB PROTOS protobuf/*.proto)
-#nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH ${CMAKE_CURRENT_SOURCE_DIR} ${PROTOS})
-#add_custom_target(generate_proto_sources DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
-#set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS}
-#    PROPERTIES GENERATED TRUE)
-
-file(GLOB SOURCES "src/*.cpp" "src/*.c" "protobuf/*.c")
-message("BEWARE => NOT GENERATING PROTOBUF")
-set(GENERATED_INCLUDES ".")
+set(NANOPB_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}")
+file(GLOB PROTOS protobuf/*.proto)
+nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH ${CMAKE_CURRENT_SOURCE_DIR} ${PROTOS})
+add_custom_target(generate_proto_sources DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
+set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS} PROPERTIES GENERATED TRUE)
 
 add_library(cspot STATIC ${SOURCES} ${PROTO_SRCS})
 # PUBLIC to propagate includes from bell to cspot dependents
 target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)
 target_compile_definitions(bell PUBLIC PB_FIELD_32BIT)
 target_link_libraries(cspot PUBLIC ${EXTRA_LIBS})
-#target_include_directories(cspot PUBLIC "include" ${CMAKE_CURRENT_BINARY_DIR} ${NANOPB_INCLUDE_DIRS})
-target_include_directories(cspot PUBLIC "include" ${GENERATED_INCLUDES} ${NANOPB_INCLUDE_DIRS})
+target_include_directories(cspot PUBLIC "include" ${CMAKE_CURRENT_BINARY_DIR} ${NANOPB_INCLUDE_DIRS} ${EXTRA_INCLUDES})

二进制
components/spotify/cspot/bell/.DS_Store


+ 126 - 0
components/spotify/cspot/bell/.gitignore

@@ -0,0 +1,126 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/c,c++,cmake,macos
+# Edit at https://www.toptal.com/developers/gitignore?templates=c,c++,cmake,macos
+
+### C ###
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+### C++ ###
+# Prerequisites
+
+# Compiled Object files
+*.slo
+
+# Precompiled Headers
+
+# Compiled Dynamic libraries
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+
+# Executables
+
+### CMake ###
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+
+### CMake Patch ###
+# External projects
+*-prefix/
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# End of https://www.toptal.com/developers/gitignore/api/c,c++,cmake,macos
+
+build/

+ 3 - 0
components/spotify/cspot/bell/.gitmodules

@@ -5,3 +5,6 @@
 [submodule "cJSON"]
 	path = cJSON
 	url = https://github.com/DaveGamble/cJSON
+[submodule "nanopb"]
+	path = nanopb
+	url = https://github.com/nanopb/nanopb

+ 192 - 59
components/spotify/cspot/bell/CMakeLists.txt

@@ -4,79 +4,205 @@ cmake_policy(SET CMP0077 NEW)
 project(bell)
 
 # Configurable options
-option(BELL_DISABLE_CODECS "Disable libhelix AAC and MP3 codecs" OFF)
-option(BELL_DISABLE_SINKS "Disable built-in audio sink implementations" OFF)
-option(BELL_USE_ALSA "Enable ALSA sink" OFF)
-option(BELL_USE_PORTAUDIO "Enable PortAudio sink" OFF)
+option(BELL_DISABLE_CODECS "Disable the entire audio codec wrapper" OFF)
+option(BELL_CODEC_AAC "Support libhelix-aac codec" ON)
+option(BELL_CODEC_MP3 "Support libhelix-mp3 codec" ON)
+option(BELL_CODEC_VORBIS "Support tremor Vorbis codec" ON)
+option(BELL_CODEC_ALAC "Support Apple ALAC codec" ON)
+option(BELL_CODEC_OPUS "Support Opus codec" ON)
+option(BELL_DISABLE_SINKS "Disable all built-in audio sink implementations" OFF)
+# These are default OFF, as they're OS-dependent (ESP32 sinks are always enabled - no external deps)
+option(BELL_SINK_ALSA "Enable ALSA audio sink" OFF)
+option(BELL_SINK_PORTAUDIO "Enable PortAudio sink" OFF)
+# cJSON wrapper
+option(BELL_DISABLE_CJSON "Disable cJSON and JSONObject completely" OFF)
 set(BELL_EXTERNAL_CJSON "" CACHE STRING "External cJSON library target name, optional")
-set(BELL_EXTERNAL_TREMOR "" CACHE STRING "External tremor library target name, optional")
+
+if(BELL_EXTERNAL_MBEDTLS)
+    set(MbedTLS_DIR ${BELL_EXTERNAL_MBEDTLS})
+	message(STATUS "Setting local mbedtls ${MbedTLS_DIR}")
+endif()
+
+# Backwards compatibility with deprecated options
+if(BELL_EXTERNAL_TREMOR)
+    message(WARNING "Deprecated Bell options used, replace BELL_EXTERNAL_TREMOR with BELL_CODEC_VORBIS=OFF")
+    set(BELL_CODEC_VORBIS OFF)
+endif()
+if(BELL_USE_ALSA)
+    message(WARNING "Deprecated Bell options used, replace BELL_USE_ALSA with BELL_SINK_ALSA")
+    set(BELL_SINK_ALSA ${BELL_USE_ALSA})
+endif()
+if(BELL_USE_PORTAUDIO)
+    message(WARNING "Deprecated Bell options used, replace BELL_USE_PORTAUDIO with BELL_SINK_PORTAUDIO")
+    set(BELL_SINK_PORTAUDIO ${BELL_USE_PORTAUDIO})
+endif()
+
+message(STATUS "Bell options:")
+message(STATUS "    Disable all codecs: ${BELL_DISABLE_CODECS}")
+if(NOT BELL_DISABLE_CODECS)
+    message(STATUS "    - AAC audio codec: ${BELL_CODEC_AAC}")
+    message(STATUS "    - MP3 audio codec: ${BELL_CODEC_MP3}")
+    message(STATUS "    - Vorbis audio codec: ${BELL_CODEC_VORBIS}")
+    message(STATUS "    - Opus audio codec: ${BELL_CODEC_OPUS}")
+    message(STATUS "    - ALAC audio codec: ${BELL_CODEC_ALAC}")
+endif()
+message(STATUS "    Disable built-in audio sinks: ${BELL_DISABLE_SINKS}")
+if(NOT BELL_DISABLE_SINKS)
+    message(STATUS "    - ALSA sink: ${BELL_SINK_ALSA}")
+    message(STATUS "    - PortAudio sink: ${BELL_SINK_PORTAUDIO}")
+endif()
+message(STATUS "    Disable cJSON and JSONObject: ${BELL_DISABLE_CJSON}")
 
 # Include nanoPB library
-set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra)
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/nanopb/extra")
 find_package(Nanopb REQUIRED)
 list(APPEND EXTRA_INCLUDES ${NANOPB_INCLUDE_DIRS})
 
 # CMake options
-set(CMAKE_CXX_STANDARD 17)
-set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
-add_definitions(-DUSE_DEFAULT_STDLIB=1)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+set(AUDIO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/audio")
+add_definitions("-DUSE_DEFAULT_STDLIB=1")
 
 # Main library sources
 file(GLOB SOURCES "src/*.cpp" "src/*.c" "nanopb/*.c")
+list(APPEND EXTRA_INCLUDES "include/platform")
+list(APPEND EXTRA_INCLUDES "include/audio/container")
 
 # Add platform specific sources
 if(ESP_PLATFORM)
     file(GLOB ESP_PLATFORM_SOURCES "src/platform/esp/*.cpp" "src/platform/esp/*.c" "src/asm/biquad_f32_ae32.S")
     list(APPEND SOURCES ${ESP_PLATFORM_SOURCES})
 endif()
-
 if(UNIX)
-    file(GLOB UNIX_PLATFORM_SOURCES "src/platform/unix/*.cpp" "src/platform/linux/TLSSocket.cpp" "src/platform/unix/*.c")
+    file(GLOB UNIX_PLATFORM_SOURCES "src/platform/unix/*.cpp" "src/platform/unix/*.c")
     list(APPEND SOURCES ${UNIX_PLATFORM_SOURCES})
 endif()
-
 if(APPLE)
-    file(GLOB APPLE_PLATFORM_SOURCES "src/platform/apple/*.cpp" "src/platform/linux/TLSSocket.cpp"  "src/platform/apple/*.c")
+    file(GLOB APPLE_PLATFORM_SOURCES "src/platform/apple/*.cpp" "src/platform/apple/*.c")
     list(APPEND SOURCES ${APPLE_PLATFORM_SOURCES})
+    list(APPEND EXTRA_INCLUDES "/usr/local/opt/mbedtls@3/include")
 endif()
-
 if(UNIX AND NOT APPLE)
     file(GLOB LINUX_PLATFORM_SOURCES "src/platform/linux/*.cpp" "src/platform/linux/*.c")
     list(APPEND SOURCES ${LINUX_PLATFORM_SOURCES})
 endif()
+if(WIN32)
+    file(GLOB WIN32_PLATFORM_SOURCES "src/platform/win32/*.cpp" "src/platform/win32/*.c")
+	list(APPEND SOURCES ${WIN32_PLATFORM_SOURCES})
+	list(APPEND EXTRA_INCLUDES "include/platform/win32")	
+endif()
+
+# A hack to make Opus keep quiet
+function(message)
+    if(NOT MESSAGE_QUIET)
+        _message(${ARGN})
+    endif()
+endfunction()
 
 if(ESP_PLATFORM)
-    # Use MBedTLS on ESP32
-    list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/CryptoOpenSSL.cpp)
-    idf_build_set_property(COMPILE_DEFINITIONS "-DBELL_USE_MBEDTLS" APPEND)
     list(APPEND EXTRA_LIBS idf::mbedtls idf::pthread idf::mdns)
     add_definitions(-Wunused-const-variable -Wchar-subscripts -Wunused-label -Wmaybe-uninitialized -Wmisleading-indentation)
 else()
-    # Use OpenSSL elsewhere
-    list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/CryptoMbedTLS.cpp)
-    find_package(OpenSSL REQUIRED)
-    find_package(Threads REQUIRED)
-    set(THREADS_PREFER_PTHREAD_FLAG ON)
-    if(OPENSSL_FOUND)
-        set(OPENSSL_USE_STATIC_LIBS TRUE)
-    endif()
-    list(APPEND EXTRA_LIBS OpenSSL::Crypto OpenSSL::SSL Threads::Threads)
+	find_package(Threads REQUIRED)
+	set(THREADS_PREFER_PTHREAD_FLAG ON)
+	list(APPEND EXTRA_LIBS Threads::Threads)
+	
+	find_package(MbedTLS REQUIRED)
+	get_target_property(MBEDTLS_INFO MbedTLS::mbedtls INTERFACE_INCLUDE_DIRECTORIES)
+	list(APPEND EXTRA_INCLUDES ${MBEDTLS_INFO})
+
+    # try to handle mbedtls when not system-wide installed		
+	if(BELL_EXTERNAL_MBEDTLS)
+		if(MSVC)
+		    set(MBEDTLS_RELEASE "RELEASE" CACHE STRING "local mbedtls version")
+		else()
+		    set(MBEDTLS_RELEASE "NOCONFIG" CACHE STRING "local mbedtls version")
+        endif()
+		message(STATUS "using local mbedtls version ${MBEDTLS_RELEASE}")		
+        get_target_property(MBEDTLS_INFO MbedTLS::mbedtls IMPORTED_LOCATION_${MBEDTLS_RELEASE})
+        list(APPEND EXTRA_LIBS ${MBEDTLS_INFO})
+        get_target_property(MBEDTLS_INFO MbedTLS::mbedx509 IMPORTED_LOCATION_${MBEDTLS_RELEASE})
+        list(APPEND EXTRA_LIBS ${MBEDTLS_INFO})
+        get_target_property(MBEDTLS_INFO MbedTLS::mbedcrypto IMPORTED_LOCATION_${MBEDTLS_RELEASE})
+        list(APPEND EXTRA_LIBS ${MBEDTLS_INFO})
+    else()	
+        list(APPEND EXTRA_LIBS mbedtls mbedcrypto mbedx509)
+    endif()	
+	
+    if(MSVC)
+        add_compile_definitions(NOMINMAX _CRT_SECURE_NO_WARNINGS)
+        add_definitions(/wd4068 /wd4244 /wd4018 /wd4101 /wd4102 /wd4142)
+    endif()	
 endif()
 
-if(BELL_DISABLE_CODECS)
-    list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/DecoderGlobals.cpp)
-else()
-    file(GLOB LIBHELIX_AAC_SOURCES "libhelix-aac/*.c")
-    file(GLOB LIBHELIX_MP3_SOURCES "libhelix-mp3/*.c")
-    list(APPEND EXTRA_INCLUDES "libhelix-aac" "libhelix-mp3")
-    list(APPEND SOURCES ${LIBHELIX_MP3_SOURCES} ${LIBHELIX_AAC_SOURCES})
+if(NOT BELL_DISABLE_CODECS)
+	file(GLOB EXTRA_SOURCES "src/audio/container/*.cpp")
+	list(APPEND SOURCES "${EXTRA_SOURCES}")
+    list(APPEND SOURCES "${AUDIO_DIR}/codec/DecoderGlobals.cpp")
+    list(APPEND SOURCES "${AUDIO_DIR}/codec/BaseCodec.cpp")
+    list(APPEND SOURCES "${AUDIO_DIR}/codec/AudioCodecs.cpp")
+    list(APPEND EXTRA_INCLUDES "include/audio/codec")
+    # AAC-LC codec
+    if(BELL_CODEC_AAC)
+        file(GLOB LIBHELIX_AAC_SOURCES "libhelix-aac/*.c")
+        list(APPEND LIBHELIX_SOURCES ${LIBHELIX_AAC_SOURCES})
+        list(APPEND EXTRA_INCLUDES "libhelix-aac")
+        list(APPEND SOURCES "${AUDIO_DIR}/codec/AACDecoder.cpp")
+        list(APPEND CODEC_FLAGS "-DBELL_CODEC_AAC")
+    endif()
+    # MP3 codec
+    if(BELL_CODEC_MP3)
+        file(GLOB LIBHELIX_MP3_SOURCES "libhelix-mp3/*.c")
+        list(APPEND LIBHELIX_SOURCES ${LIBHELIX_MP3_SOURCES})
+        list(APPEND EXTRA_INCLUDES "libhelix-mp3")
+        list(APPEND SOURCES "${AUDIO_DIR}/codec/MP3Decoder.cpp")
+        list(APPEND CODEC_FLAGS "-DBELL_CODEC_MP3")
+    endif()
 
+    # MP3 codec
+    if(BELL_CODEC_ALAC)
+        file(GLOB ALAC_SOURCES "alac/*.c" "alac/*.cpp")
+        list(APPEND ALAC_SOURCES ${ALAC_SOURCES})
+        list(APPEND EXTRA_INCLUDES "alac")
+        # list(APPEND SOURCES "${AUDIO_DIR}/codec/ALACDecoder.cpp")
+        list(APPEND CODEC_FLAGS "-DBELL_CODEC_ALAC")
+    endif()
+    # libhelix Cygwin workaround
     if(CYGWIN)
         # Both Cygwin and ESP are Unix-like so this seems to work (or, at least, compile)
-        set_source_files_properties(src/DecoderGlobals.cpp PROPERTIES COMPILE_FLAGS -DESP_PLATFORM)
-        set_source_files_properties(${LIBHELIX_AAC_SOURCES} PROPERTIES COMPILE_FLAGS -DESP_PLATFORM)
-        set_source_files_properties(${LIBHELIX_MP3_SOURCES} PROPERTIES COMPILE_FLAGS -DESP_PLATFORM)
+        set_source_files_properties("${AUDIO_DIR}/codec/DecoderGlobals.cpp" ${LIBHELIX_SOURCES} PROPERTIES COMPILE_FLAGS "-DESP_PLATFORM")
+    endif()
+    list(APPEND SOURCES ${LIBHELIX_SOURCES})
+    list(APPEND SOURCES ${ALAC_SOURCES})
+    # Vorbis codec
+    if(BELL_CODEC_VORBIS)
+        file(GLOB TREMOR_SOURCES "tremor/*.c")
+        list(REMOVE_ITEM TREMOR_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/tremor/ivorbisfile_example.c")
+        list(APPEND SOURCES ${TREMOR_SOURCES})
+        list(APPEND EXTRA_INCLUDES "tremor")
+        list(APPEND SOURCES "${AUDIO_DIR}/codec/VorbisDecoder.cpp")
+        list(APPEND CODEC_FLAGS "-DBELL_CODEC_VORBIS")
+    endif()
+    # Opus codec
+    if(BELL_CODEC_OPUS)
+        set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF CACHE BOOL "")
+        set(OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
+        set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF CACHE BOOL "")
+        set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
+        set(MESSAGE_QUIET ON)
+        add_subdirectory("opus")
+        unset(MESSAGE_QUIET)
+        target_compile_options(opus PRIVATE "-O3")
+        list(APPEND EXTRA_LIBS Opus::opus)
+        list(APPEND SOURCES "${AUDIO_DIR}/codec/OPUSDecoder.cpp")
+        list(APPEND CODEC_FLAGS -DBELL_CODEC_OPUS)
     endif()
+    # Enable global codecs
+    string(REPLACE ";" " " CODEC_FLAGS "${CODEC_FLAGS}")
+    set_source_files_properties("${AUDIO_DIR}/codec/AudioCodecs.cpp" PROPERTIES COMPILE_FLAGS "${CODEC_FLAGS}")
+elseif(BELL_EXTERNAL_TREMOR) 	
+    list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_TREMOR})
 endif()
 
 if(NOT BELL_DISABLE_SINKS)
@@ -85,45 +211,52 @@ if(NOT BELL_DISABLE_SINKS)
         set(PLATFORM "esp")
     endif()
     # Add all built-in audio sinks
-    file(GLOB SINK_SOURCES "src/sinks/${PLATFORM}/*.cpp" "src/sinks/${PLATFORM}/*.c")
+    file(GLOB SINK_SOURCES "${AUDIO_DIR}/sinks/${PLATFORM}/*.cpp" "${AUDIO_DIR}/sinks/${PLATFORM}/*.c")
+    list(APPEND EXTRA_INCLUDES "include/audio/sinks/${PLATFORM}")
     # Find ALSA if required, else remove the sink
-    if(BELL_USE_ALSA)
+    if(BELL_SINK_ALSA)
         find_package(ALSA REQUIRED)
         list(APPEND EXTRA_INCLUDES ${ALSA_INCLUDE_DIRS})
         list(APPEND EXTRA_LIBS ${ALSA_LIBRARIES})
     else()
-        list(REMOVE_ITEM SINK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/sinks/unix/ALSAAudioSink.cpp)
+        list(REMOVE_ITEM SINK_SOURCES "${AUDIO_DIR}/sinks/unix/ALSAAudioSink.cpp")
     endif()
     # Find PortAudio if required, else remove the sink
-    if(BELL_USE_PORTAUDIO)
-        find_package(portaudio REQUIRED)
-        list(APPEND EXTRA_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
-        list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES})
+    if(BELL_SINK_PORTAUDIO)
+		if(WIN32)
+			list(APPEND EXTRA_INCLUDES "portaudio/include")
+			if(NOT "${CMAKE_GENERATOR}" MATCHES "(Win64|IA64)")
+				list(APPEND EXTRA_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/portaudio/portaudio_win32.lib")
+			else()
+				list(APPEND EXTRA_LIBS "${CMAKE_CURRENT_SOURCE_DIR}/portaudio/portaudio_x64.lib")
+			endif()
+		else()
+			find_package(portaudio REQUIRED)
+			list(APPEND EXTRA_INCLUDES ${PORTAUDIO_INCLUDE_DIRS})
+			list(APPEND EXTRA_LIBS ${PORTAUDIO_LIBRARIES})
+		endif()
     else()
-        list(REMOVE_ITEM SINK_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/sinks/unix/PortAudioSink.cpp)
+        list(REMOVE_ITEM SINK_SOURCES "${AUDIO_DIR}/sinks/unix/PortAudioSink.cpp")
     endif()
     list(APPEND SOURCES ${SINK_SOURCES})
-    list(APPEND EXTRA_INCLUDES "include/sinks/${PLATFORM}")
-endif()
-
-if(BELL_EXTERNAL_CJSON)
-    list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
-else()
-    list(APPEND EXTRA_INCLUDES "cJSON")
-    list(APPEND SOURCES "cJSON/cJSON.c")
 endif()
 
-if(BELL_EXTERNAL_TREMOR)
-    list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_TREMOR})
+if(BELL_DISABLE_CJSON)
+    list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/JSONObject.cpp")
 else()
-    file(GLOB TREMOR_SOURCES "tremor/*.c")
-    list(REMOVE_ITEM TREMOR_SOURCES "tremor/ivorbisfile_example.c")
-    list(APPEND EXTRA_INCLUDES "tremor")
-    list(APPEND SOURCES ${TREMOR_SOURCES})
+    if(BELL_EXTERNAL_CJSON)
+        list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_CJSON})
+    else()
+        list(APPEND SOURCES "cJSON/cJSON.c")
+        list(APPEND EXTRA_INCLUDES "cJSON")
+    endif()
 endif()
 
 add_library(bell STATIC ${SOURCES})
 # PUBLIC to propagate esp-idf includes to bell dependents
 target_link_libraries(bell PUBLIC ${EXTRA_LIBS})
-target_include_directories(bell PUBLIC "include" "include/platform" ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
+target_include_directories(bell PUBLIC "include" ${EXTRA_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR})
 target_compile_definitions(bell PUBLIC PB_ENABLE_MALLOC)
+if(WIN32)
+	target_compile_definitions(bell PUBLIC PB_NO_STATIC_ASSERT)
+endif()	

+ 23 - 10
components/spotify/cspot/bell/cJSON/CMakeLists.txt

@@ -121,6 +121,7 @@ set(SOURCES cJSON.c)
 option(BUILD_SHARED_AND_STATIC_LIBS "Build both shared and static libraries" Off)
 option(CJSON_OVERRIDE_BUILD_SHARED_LIBS "Override BUILD_SHARED_LIBS with CJSON_BUILD_SHARED_LIBS" OFF)
 option(CJSON_BUILD_SHARED_LIBS "Overrides BUILD_SHARED_LIBS if CJSON_OVERRIDE_BUILD_SHARED_LIBS is enabled" ON)
+option(ENABLE_CJSON_VERSION_SO "Enables cJSON so version" ON)
 
 if ((CJSON_OVERRIDE_BUILD_SHARED_LIBS AND CJSON_BUILD_SHARED_LIBS) OR ((NOT CJSON_OVERRIDE_BUILD_SHARED_LIBS) AND BUILD_SHARED_LIBS))
     set(CJSON_LIBRARY_TYPE SHARED)
@@ -155,17 +156,23 @@ install(TARGETS "${CJSON_LIB}"
     INCLUDES DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}"
 )
 if (BUILD_SHARED_AND_STATIC_LIBS)
-    install(TARGETS "${CJSON_LIB}-static" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}")
+    install(TARGETS "${CJSON_LIB}-static"
+    EXPORT "${CJSON_LIB}" 
+    ARCHIVE DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}"
+    INCLUDES DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}"
+)
 endif()
 if(ENABLE_TARGET_EXPORT)
     # export library information for CMake projects
     install(EXPORT "${CJSON_LIB}" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cJSON")
 endif()
 
-set_target_properties("${CJSON_LIB}"
-    PROPERTIES
-        SOVERSION "${CJSON_VERSION_SO}"
-        VERSION "${PROJECT_VERSION}")
+if(ENABLE_CJSON_VERSION_SO)
+    set_target_properties("${CJSON_LIB}"
+        PROPERTIES
+            SOVERSION "${CJSON_VERSION_SO}"
+            VERSION "${PROJECT_VERSION}")
+endif()
 
 #cJSON_Utils
 option(ENABLE_CJSON_UTILS "Enable building the cJSON_Utils library." OFF)
@@ -198,7 +205,11 @@ if(ENABLE_CJSON_UTILS)
         INCLUDES DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}"
     )
     if (BUILD_SHARED_AND_STATIC_LIBS)
-        install(TARGETS "${CJSON_UTILS_LIB}-static" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}")
+        install(TARGETS "${CJSON_UTILS_LIB}-static" 
+        EXPORT "${CJSON_UTILS_LIB}" 
+        ARCHIVE DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}"
+        INCLUDES DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}"
+        )
     endif()
     install(FILES cJSON_Utils.h DESTINATION "${CMAKE_INSTALL_FULL_INCLUDEDIR}/cjson")
     install (FILES "${CMAKE_CURRENT_BINARY_DIR}/libcjson_utils.pc" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig")
@@ -207,10 +218,12 @@ if(ENABLE_CJSON_UTILS)
       install(EXPORT "${CJSON_UTILS_LIB}" DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/cmake/cJSON")
     endif()
 
-    set_target_properties("${CJSON_UTILS_LIB}"
-        PROPERTIES
-            SOVERSION "${CJSON_UTILS_VERSION_SO}"
-            VERSION "${PROJECT_VERSION}")
+    if(ENABLE_CJSON_VERSION_SO)
+        set_target_properties("${CJSON_UTILS_LIB}"
+            PROPERTIES
+                SOVERSION "${CJSON_UTILS_VERSION_SO}"
+                VERSION "${PROJECT_VERSION}")
+    endif()
 endif()
 
 # create the other package config files

+ 16 - 7
components/spotify/cspot/bell/cJSON/cJSON.c

@@ -96,9 +96,9 @@ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void)
     return (const char*) (global_error.json + global_error.position);
 }
 
-CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) 
+CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)
 {
-    if (!cJSON_IsString(item)) 
+    if (!cJSON_IsString(item))
     {
         return NULL;
     }
@@ -106,9 +106,9 @@ CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item)
     return item->valuestring;
 }
 
-CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) 
+CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item)
 {
-    if (!cJSON_IsNumber(item)) 
+    if (!cJSON_IsNumber(item))
     {
         return (double) NAN;
     }
@@ -511,7 +511,7 @@ static unsigned char* ensure(printbuffer * const p, size_t needed)
 
             return NULL;
         }
-        
+
         memcpy(newbuffer, p->buffer, p->offset + 1);
         p->hooks.deallocate(p->buffer);
     }
@@ -562,6 +562,10 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
     {
         length = sprintf((char*)number_buffer, "null");
     }
+	else if(d == (double)item->valueint)
+	{
+		length = sprintf((char*)number_buffer, "%d", item->valueint);
+	}
     else
     {
         /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */
@@ -1103,7 +1107,7 @@ CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer
     }
 
     buffer.content = (const unsigned char*)value;
-    buffer.length = buffer_length; 
+    buffer.length = buffer_length;
     buffer.offset = 0;
     buffer.hooks = global_hooks;
 
@@ -2357,6 +2361,11 @@ static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSO
         cJSON_free(replacement->string);
     }
     replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
+    if (replacement->string == NULL)
+    {
+        return false;
+    }
+
     replacement->type &= ~cJSON_StringIsConst;
 
     return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement);
@@ -2689,7 +2698,7 @@ CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int co
     if (a && a->child) {
         a->child->prev = n;
     }
-    
+
     return a;
 }
 

+ 7 - 0
components/spotify/cspot/bell/cJSON/cJSON.h

@@ -279,6 +279,13 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
 /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
 CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
 
+/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
+#define cJSON_SetBoolValue(object, boolValue) ( \
+    (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
+    (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
+    cJSON_Invalid\
+)
+
 /* Macro for iterating over an array or object */
 #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
 

+ 78 - 17
components/spotify/cspot/bell/cJSON/tests/misc_tests.c

@@ -28,7 +28,6 @@
 #include "unity/src/unity.h"
 #include "common.h"
 
-
 static void cjson_array_foreach_should_loop_over_arrays(void)
 {
     cJSON array[1];
@@ -77,7 +76,6 @@ static void cjson_get_object_item_should_get_object_items(void)
     found = cJSON_GetObjectItem(item, NULL);
     TEST_ASSERT_NULL_MESSAGE(found, "Failed to fail on NULL string.");
 
-
     found = cJSON_GetObjectItem(item, "one");
     TEST_ASSERT_NOT_NULL_MESSAGE(found, "Failed to find first item.");
     TEST_ASSERT_EQUAL_DOUBLE(found->valuedouble, 1);
@@ -127,7 +125,8 @@ static void cjson_get_object_item_case_sensitive_should_get_object_items(void)
     cJSON_Delete(item);
 }
 
-static void cjson_get_object_item_should_not_crash_with_array(void) {
+static void cjson_get_object_item_should_not_crash_with_array(void)
+{
     cJSON *array = NULL;
     cJSON *found = NULL;
     array = cJSON_Parse("[1]");
@@ -138,7 +137,8 @@ static void cjson_get_object_item_should_not_crash_with_array(void) {
     cJSON_Delete(array);
 }
 
-static void cjson_get_object_item_case_sensitive_should_not_crash_with_array(void) {
+static void cjson_get_object_item_case_sensitive_should_not_crash_with_array(void)
+{
     cJSON *array = NULL;
     cJSON *found = NULL;
     array = cJSON_Parse("[1]");
@@ -302,7 +302,6 @@ static void cjson_replace_item_via_pointer_should_replace_items(void)
     cJSON_AddItemToArray(array, middle);
     cJSON_AddItemToArray(array, end);
 
-
     memset(replacements, '\0', sizeof(replacements));
 
     /* replace beginning */
@@ -329,7 +328,7 @@ static void cjson_replace_item_via_pointer_should_replace_items(void)
 
 static void cjson_replace_item_in_object_should_preserve_name(void)
 {
-    cJSON root[1] = {{ NULL, NULL, NULL, 0, NULL, 0, 0, NULL }};
+    cJSON root[1] = {{NULL, NULL, NULL, 0, NULL, 0, 0, NULL}};
     cJSON *child = NULL;
     cJSON *replacement = NULL;
     cJSON_bool flag = false;
@@ -339,7 +338,7 @@ static void cjson_replace_item_in_object_should_preserve_name(void)
     replacement = cJSON_CreateNumber(2);
     TEST_ASSERT_NOT_NULL(replacement);
 
-    flag  = cJSON_AddItemToObject(root, "child", child);
+    flag = cJSON_AddItemToObject(root, "child", child);
     TEST_ASSERT_TRUE_MESSAGE(flag, "add item to object failed");
     cJSON_ReplaceItemInObject(root, "child", replacement);
 
@@ -435,7 +434,7 @@ static void cjson_functions_should_not_crash_with_null_pointers(void)
     cJSON_Delete(item);
 }
 
-static void * CJSON_CDECL failing_realloc(void *pointer, size_t size)
+static void *CJSON_CDECL failing_realloc(void *pointer, size_t size)
 {
     (void)size;
     (void)pointer;
@@ -445,7 +444,7 @@ static void * CJSON_CDECL failing_realloc(void *pointer, size_t size)
 static void ensure_should_fail_on_failed_realloc(void)
 {
     printbuffer buffer = {NULL, 10, 0, 0, false, false, {&malloc, &free, &failing_realloc}};
-    buffer.buffer = (unsigned char*)malloc(100);
+    buffer.buffer = (unsigned char *)malloc(100);
     TEST_ASSERT_NOT_NULL(buffer.buffer);
 
     TEST_ASSERT_NULL_MESSAGE(ensure(&buffer, 200), "Ensure didn't fail with failing realloc.");
@@ -454,7 +453,7 @@ static void ensure_should_fail_on_failed_realloc(void)
 static void skip_utf8_bom_should_skip_bom(void)
 {
     const unsigned char string[] = "\xEF\xBB\xBF{}";
-    parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
+    parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
     buffer.content = string;
     buffer.length = sizeof(string);
     buffer.hooks = global_hooks;
@@ -466,7 +465,7 @@ static void skip_utf8_bom_should_skip_bom(void)
 static void skip_utf8_bom_should_not_skip_bom_if_not_at_beginning(void)
 {
     const unsigned char string[] = " \xEF\xBB\xBF{}";
-    parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } };
+    parse_buffer buffer = {0, 0, 0, 0, {0, 0, 0}};
     buffer.content = string;
     buffer.length = sizeof(string);
     buffer.hooks = global_hooks;
@@ -496,12 +495,13 @@ static void cjson_get_number_value_should_get_a_number(void)
     TEST_ASSERT_EQUAL_DOUBLE(cJSON_GetNumberValue(number), number->valuedouble);
     TEST_ASSERT_DOUBLE_IS_NAN(cJSON_GetNumberValue(string));
     TEST_ASSERT_DOUBLE_IS_NAN(cJSON_GetNumberValue(NULL));
-    
+
     cJSON_Delete(number);
     cJSON_Delete(string);
 }
 
-static void cjson_create_string_reference_should_create_a_string_reference(void) {
+static void cjson_create_string_reference_should_create_a_string_reference(void)
+{
     const char *string = "I am a string!";
 
     cJSON *string_reference = cJSON_CreateStringReference(string);
@@ -511,7 +511,8 @@ static void cjson_create_string_reference_should_create_a_string_reference(void)
     cJSON_Delete(string_reference);
 }
 
-static void cjson_create_object_reference_should_create_an_object_reference(void) {
+static void cjson_create_object_reference_should_create_an_object_reference(void)
+{
     cJSON *number_reference = NULL;
     cJSON *number_object = cJSON_CreateObject();
     cJSON *number = cJSON_CreateNumber(42);
@@ -529,7 +530,8 @@ static void cjson_create_object_reference_should_create_an_object_reference(void
     cJSON_Delete(number_reference);
 }
 
-static void cjson_create_array_reference_should_create_an_array_reference(void) {
+static void cjson_create_array_reference_should_create_an_array_reference(void)
+{
     cJSON *number_reference = NULL;
     cJSON *number_array = cJSON_CreateArray();
     cJSON *number = cJSON_CreateNumber(42);
@@ -566,7 +568,7 @@ static void cjson_add_item_to_object_should_not_use_after_free_when_string_is_al
 {
     cJSON *object = cJSON_CreateObject();
     cJSON *number = cJSON_CreateNumber(42);
-    char *name = (char*)cJSON_strdup((const unsigned char*)"number", &global_hooks);
+    char *name = (char *)cJSON_strdup((const unsigned char *)"number", &global_hooks);
 
     TEST_ASSERT_NOT_NULL(object);
     TEST_ASSERT_NOT_NULL(number);
@@ -626,7 +628,7 @@ static void cjson_set_valuestring_to_object_should_not_leak_memory(void)
     cJSON *item2 = cJSON_CreateStringReference(reference_valuestring);
     char *ptr1 = NULL;
     char *return_value = NULL;
-    
+
     cJSON_AddItemToObject(root, "one", item1);
     cJSON_AddItemToObject(root, "two", item2);
 
@@ -650,6 +652,64 @@ static void cjson_set_valuestring_to_object_should_not_leak_memory(void)
     cJSON_Delete(root);
 }
 
+static void cjson_set_bool_value_must_not_break_objects(void)
+{
+    cJSON *bobj, *sobj, *oobj, *refobj = NULL;
+
+    TEST_ASSERT_TRUE((cJSON_SetBoolValue(refobj, 1) == cJSON_Invalid));
+
+    bobj = cJSON_CreateFalse();
+    TEST_ASSERT_TRUE(cJSON_IsFalse(bobj));
+    TEST_ASSERT_TRUE((cJSON_SetBoolValue(bobj, 1) == cJSON_True));
+    TEST_ASSERT_TRUE(cJSON_IsTrue(bobj));
+    cJSON_SetBoolValue(bobj, 1);
+    TEST_ASSERT_TRUE(cJSON_IsTrue(bobj));
+    TEST_ASSERT_TRUE((cJSON_SetBoolValue(bobj, 0) == cJSON_False));
+    TEST_ASSERT_TRUE(cJSON_IsFalse(bobj));
+    cJSON_SetBoolValue(bobj, 0);
+    TEST_ASSERT_TRUE(cJSON_IsFalse(bobj));
+
+    sobj = cJSON_CreateString("test");
+    TEST_ASSERT_TRUE(cJSON_IsString(sobj));
+    cJSON_SetBoolValue(sobj, 1);
+    TEST_ASSERT_TRUE(cJSON_IsString(sobj));
+    cJSON_SetBoolValue(sobj, 0);
+    TEST_ASSERT_TRUE(cJSON_IsString(sobj));
+
+    oobj = cJSON_CreateObject();
+    TEST_ASSERT_TRUE(cJSON_IsObject(oobj));
+    cJSON_SetBoolValue(oobj, 1);
+    TEST_ASSERT_TRUE(cJSON_IsObject(oobj));
+    cJSON_SetBoolValue(oobj, 0);
+    TEST_ASSERT_TRUE(cJSON_IsObject(oobj));
+
+    refobj = cJSON_CreateStringReference("conststring");
+    TEST_ASSERT_TRUE(cJSON_IsString(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_SetBoolValue(refobj, 1);
+    TEST_ASSERT_TRUE(cJSON_IsString(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_SetBoolValue(refobj, 0);
+    TEST_ASSERT_TRUE(cJSON_IsString(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_Delete(refobj);
+
+    refobj = cJSON_CreateObjectReference(oobj);
+    TEST_ASSERT_TRUE(cJSON_IsObject(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_SetBoolValue(refobj, 1);
+    TEST_ASSERT_TRUE(cJSON_IsObject(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_SetBoolValue(refobj, 0);
+    TEST_ASSERT_TRUE(cJSON_IsObject(refobj));
+    TEST_ASSERT_TRUE(refobj->type & cJSON_IsReference);
+    cJSON_Delete(refobj);
+
+    cJSON_Delete(oobj);
+    cJSON_Delete(bobj);
+    cJSON_Delete(sobj);
+}
+
 int CJSON_CDECL main(void)
 {
     UNITY_BEGIN();
@@ -679,6 +739,7 @@ int CJSON_CDECL main(void)
     RUN_TEST(cjson_add_item_to_object_should_not_use_after_free_when_string_is_aliased);
     RUN_TEST(cjson_delete_item_from_array_should_not_broken_list_structure);
     RUN_TEST(cjson_set_valuestring_to_object_should_not_leak_memory);
+    RUN_TEST(cjson_set_bool_value_must_not_break_objects);
 
     return UNITY_END();
 }

+ 53 - 5
components/spotify/cspot/bell/include/BaseHTTPServer.h

@@ -6,7 +6,14 @@
 #include <map>
 #include <memory>
 #include <functional>
+#include <iostream>
 #include <vector>
+#include <cstring>
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#endif
 
 namespace bell {
 
@@ -20,6 +27,40 @@ class ResponseReader {
     virtual void close() = 0;
 };
 
+class RequestBodyReader : public ResponseReader {
+  public:
+    std::vector<uint8_t> partialBuffer;
+    int fd = 0;
+    size_t contentLength = 0;
+    size_t sizeRead = 0;
+
+    RequestBodyReader(size_t contentLength, int fd, std::vector<uint8_t> &partialBuffer) {
+        this->contentLength = contentLength;
+        this->partialBuffer = partialBuffer;
+        this->fd = fd;
+    };
+
+
+    size_t read(char *buffer, size_t size) {
+        if (sizeRead < partialBuffer.size()) {
+            size_t toRead = std::min(size, partialBuffer.size() - sizeRead);
+            memcpy(buffer, &partialBuffer[sizeRead], toRead);
+            sizeRead += toRead;
+            return toRead;
+        } else {
+            size_t toRead = std::min(size, contentLength - sizeRead);
+            size_t read = recv(fd, buffer, toRead, 0);
+            sizeRead += read;
+            return read;
+        }
+    }
+
+    void close() {
+    }
+
+    size_t getTotalSize() { return contentLength; }
+};
+
 class FileResponseReader : public ResponseReader {
   public:
     FILE *file;
@@ -30,7 +71,7 @@ class FileResponseReader : public ResponseReader {
         fileSize = ftell(file);   // get current file pointer
         fseek(file, 0, SEEK_SET); // seek back to beginning of file
     };
-    ~FileResponseReader() { fclose(file); };
+
 
     size_t read(char *buffer, size_t size) {
         return fread(buffer, 1, size, file);
@@ -48,10 +89,13 @@ enum class RequestType { GET, POST };
 struct HTTPRequest {
     std::map<std::string, std::string> urlParams;
     std::map<std::string, std::string> queryParams;
+    std::unique_ptr<ResponseReader> responseReader = std::unique_ptr<RequestBodyReader>(nullptr);
+
     std::string body;
     std::string url;
     int handlerId;
     int connection;
+    int contentLength;
 };
 
 struct HTTPResponse {
@@ -64,19 +108,22 @@ struct HTTPResponse {
     std::unique_ptr<ResponseReader> responseReader;
 };
 
-typedef std::function<void(HTTPRequest &)> httpHandler;
+typedef std::function<void(std::unique_ptr<bell::HTTPRequest>)> httpHandler;
+
 struct HTTPRoute {
     RequestType requestType;
     httpHandler handler;
+    bool readBodyToStr;
 };
 
 struct HTTPConnection {
+    int fd = 0;
     std::vector<uint8_t> buffer;
-    std::string currentLine = "";
+    std::vector<uint8_t> partialBuffer = std::vector<uint8_t>();
     int contentLength = 0;
-    bool isReadingBody = false;
     std::string httpMethod;
     bool toBeClosed = false;
+    bool headersRead = false;
     bool isEventConnection = false;
     bool isCaptivePortal = false;
 };
@@ -96,10 +143,11 @@ public:
      *
      * @param requestType GET or POST
      * @param endpoint registering under
+     * @param readResponseToStr if true, response will be read to string, otherwise it will return a reader object
      * httpHandler lambda to be called when given endpoint gets executed
      */
     virtual void registerHandler(RequestType requestType, const std::string & endpoint,
-                                 httpHandler) = 0;
+                                 httpHandler, bool readResponseToStr = true) = 0;
 
     /**
      * Writes given response to a fd

+ 10 - 5
components/spotify/cspot/bell/include/BellLogger.h

@@ -3,6 +3,7 @@
 
 #include <stdio.h>
 #include <stdarg.h>
+#include <string.h>
 #include <string>
 #include <memory>
 
@@ -18,7 +19,7 @@ namespace bell
         virtual void info(std::string filename, int line, std::string submodule, const char *format, ...) = 0;
     };
 
-    extern std::shared_ptr<bell::AbstractLogger> bellGlobalLogger;
+    extern bell::AbstractLogger* bellGlobalLogger;
     class BellLogger : public bell::AbstractLogger
     {
     public:
@@ -81,23 +82,27 @@ namespace bell
 
         void printFilename(std::string filename)
         {
+#ifdef _WIN32
+            std::string basenameStr(filename.substr(filename.rfind("\\") + 1));
+#else
             std::string basenameStr(filename.substr(filename.rfind("/") + 1));
+#endif
             unsigned long hash = 5381;
             for (char const &c : basenameStr)
             {
                 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
             }
 
-            printf("\e[0;%dm", allColors[hash % NColors]);
+            printf("\033[0;%dm", allColors[hash % NColors]);
 
             printf("%s", basenameStr.c_str());
             printf(colorReset);
         }
 
     private:
-        static constexpr const char *colorReset = "\e[0m";
-        static constexpr const char *colorRed = "\e[0;31m";
-        static constexpr const char *colorBlue = "\e[0;34m";
+        static constexpr const char *colorReset = "\033[0m";
+        static constexpr const char *colorRed = "\033[0;31m";
+        static constexpr const char *colorBlue = "\033[0;34m";
         static constexpr const int NColors = 15;
         static constexpr int allColors[NColors] = {31, 32, 33, 34, 35, 36, 37, 90, 91, 92, 93, 94, 95, 96, 97};
     };

+ 17 - 1
components/spotify/cspot/bell/include/Task.h → components/spotify/cspot/bell/include/BellTask.h

@@ -7,9 +7,12 @@
 #include <freertos/FreeRTOS.h>
 #include <freertos/timers.h>
 #include <freertos/task.h>
+#elif _WIN32
+#include <winsock2.h>
+#else
+#include <pthread.h>
 #endif
 
-#include <pthread.h>
 #include <string>
 
 namespace bell
@@ -62,14 +65,23 @@ namespace bell
                 esp_pthread_set_cfg(&cfg);
             }
 #endif
+#if _WIN32
+            thread = CreateThread(NULL, stackSize, (LPTHREAD_START_ROUTINE) taskEntryFunc, this, 0, NULL);
+            return thread != NULL;
+#else
             return (pthread_create(&thread, NULL, taskEntryFunc, this) == 0);
+#endif
         }
 
     protected:
         virtual void runTask() = 0;
 
     private:
+#if _WIN32
+        HANDLE thread;
+#else
         pthread_t thread;
+#endif
 #ifdef ESP_PLATFORM
 		int priority;
         StaticTask_t *xTaskBuffer;
@@ -96,7 +108,11 @@ namespace bell
         {
 			Task* self = (Task*) This;
 			self->runTask();
+#if _WIN32
+            WaitForSingleObject(self->thread, INFINITE);
+#else
 			pthread_join(self->thread, NULL);
+#endif
             return NULL;
         }
     };

+ 3 - 0
components/spotify/cspot/bell/include/BellUtils.h

@@ -18,6 +18,9 @@ void freeAndNull(void *&ptr);
 #define BELL_SLEEP_MS(ms) vTaskDelay(ms / portTICK_PERIOD_MS)
 #define BELL_YIELD() taskYIELD()
 
+#elif defined(_WIN32)
+#define BELL_SLEEP_MS(ms) Sleep(ms)
+#define BELL_YIELD() ;
 #else
 #include <unistd.h>
 

+ 127 - 0
components/spotify/cspot/bell/include/BufferedStream.h

@@ -0,0 +1,127 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-7.
+
+#pragma once
+
+#include "ByteStream.h"
+#include "BellTask.h"
+#include "WrappedSemaphore.h"
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+
+/**
+ * This class implements a wrapper around an arbitrary bell::ByteStream,
+ * providing a circular reading buffer with configurable thresholds.
+ *
+ * The BufferedStream runs a bell::Task when it's started, so the caller can
+ * access the buffer's data asynchronously, whenever needed. The buffer is refilled
+ * automatically from source stream.
+ *
+ * The class implements bell::ByteStream's methods, although for proper functioning,
+ * the caller code should be modified to check isReady() and isNotReady() flags.
+ *
+ * If the actual reading code can't be modified, waitForReady allows to wait for buffer readiness
+ * during reading. Keep in mind that using the semaphore is probably more resource effective.
+ *
+ * The source stream (passed to open() or returned by the reader) should implement the read()
+ * method correctly, such as that 0 is returned if, and only if the stream ends.
+ */
+class BufferedStream : public bell::ByteStream, bell::Task {
+  public:
+	typedef std::shared_ptr<bell::ByteStream> StreamPtr;
+	typedef std::function<StreamPtr(uint32_t rangeStart)> StreamReader;
+
+  public:
+	/**
+	 * @param taskName name to use for the reading task
+	 * @param bufferSize total size of the reading buffer
+	 * @param readThreshold how much can be read before refilling the buffer
+	 * @param readSize amount of bytes to read from the source each time
+	 * @param readyThreshold minimum amount of available bytes to report isReady()
+	 * @param notReadyThreshold maximum amount of available bytes to report isNotReady()
+	 * @param waitForReady whether to wait for the buffer to be ready during reading
+	 * @param endWithSource whether to end the streaming as soon as source returns 0 from read()
+	 */
+	BufferedStream(
+		const std::string &taskName,
+		uint32_t bufferSize,
+		uint32_t readThreshold,
+		uint32_t readSize,
+		uint32_t readyThreshold,
+		uint32_t notReadyThreshold,
+		bool waitForReady = false);
+	~BufferedStream() override;
+	bool open(const StreamPtr &stream);
+	bool open(const StreamReader &newReader, uint32_t initialOffset = 0);
+	void close() override;
+
+	// inherited methods
+  public:
+	/**
+	 * Read len bytes from the buffer to dst. If waitForReady is enabled
+	 * and readAvailable is lower than notReadyThreshold, the function
+	 * will block until readyThreshold bytes is available.
+	 *
+	 * @returns number of bytes copied to dst (might be lower than len,
+	 * if the buffer does not contain len bytes available), or 0 if the source
+	 * stream is already closed and there is no reader attached.
+	 */
+	size_t read(uint8_t *dst, size_t len) override;
+	size_t skip(size_t len) override;
+	size_t position() override;
+	size_t size() override;
+
+	// stream status
+  public:
+	/**
+	 * Total amount of bytes served to read().
+	 */
+	uint32_t readTotal;
+	/**
+	 * Total amount of bytes read from source.
+	 */
+	uint32_t bufferTotal;
+	/**
+	 * Amount of bytes available to read from the buffer.
+	 */
+	std::atomic<uint32_t> readAvailable;
+	/**
+	 * Whether the caller should start reading the data. This indicates that a safe
+	 * amount (determined by readyThreshold) of data is available in the buffer.
+	 */
+	bool isReady() const;
+	/**
+	 * Whether the caller should stop reading the data. This indicates that the amount of data
+	 * available for reading is decreasing to a non-safe value, as data is being read
+	 * faster than it can be buffered.
+	 */
+	bool isNotReady() const;
+	/**
+	 * Semaphore that is given when the buffer becomes ready (isReady() == true). Caller can
+	 * wait for the semaphore instead of continuously querying isReady().
+	 */
+	WrappedSemaphore readySem;
+
+  private:
+	std::mutex runningMutex;
+	bool running = false;
+	bool terminate = false;
+	WrappedSemaphore readSem; // signal to start writing to buffer after reading from it
+	std::mutex readMutex;	  // mutex for locking read operations during writing, and vice versa
+	uint32_t bufferSize;
+	uint32_t readAt;
+	uint32_t readSize;
+	uint32_t readyThreshold;
+	uint32_t notReadyThreshold;
+	bool waitForReady;
+	uint8_t *buf;
+	uint8_t *bufEnd;
+	uint8_t *bufReadPtr;
+	uint8_t *bufWritePtr;
+	StreamPtr source;
+	StreamReader reader;
+	void runTask() override;
+	void reset();
+	uint32_t lengthBetween(uint8_t *me, uint8_t *other);
+};

+ 71 - 7
components/spotify/cspot/bell/include/Crypto.h

@@ -1,14 +1,78 @@
 #ifndef BELL_CRYPTO_H
 #define BELL_CRYPTO_H
 
+#define Crypto CryptoMbedTLS
+
 #include <vector>
 #include <string>
+#include <memory>
+
+#include <mbedtls/base64.h>
+#include <mbedtls/bignum.h>
+#include <mbedtls/md.h>
+#include <mbedtls/aes.h>
+#include <mbedtls/pkcs5.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/ctr_drbg.h>
+
+
+#define DH_KEY_SIZE 96
+
+static unsigned char DHPrime[] = {
+    /* Well-known Group 1, 768-bit prime */
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
+    0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
+    0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e,
+    0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
+    0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
+    0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a,
+    0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14,
+    0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
+    0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4,
+    0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+static unsigned char DHGenerator[1] = {2};
+
+class CryptoMbedTLS {
+private:
+    mbedtls_md_context_t sha1Context;
+    mbedtls_aes_context aesCtx;
+public:
+    CryptoMbedTLS();
+    ~CryptoMbedTLS();
+    // Base64
+    std::vector<uint8_t> base64Decode(const std::string& data);
+    std::string base64Encode(const std::vector<uint8_t>& data);
+
+    // Sha1
+    void sha1Init();
+    void sha1Update(const std::string& s);
+    void sha1Update(const std::vector<uint8_t>& vec);
+    std::string sha1Final();
+    std::vector<uint8_t> sha1FinalBytes();
+
+    // HMAC SHA1
+    std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
+
+    // AES CTR
+    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* data, size_t nbytes);
+    
+    // AES ECB
+    void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);
+
+    // Diffie Hellman
+    std::vector<uint8_t> publicKey;
+    std::vector<uint8_t> privateKey;
+    void dhInit();
+    std::vector<uint8_t> dhCalculateShared(const std::vector<uint8_t>& remoteKey);
+
+    // PBKDF2
+    std::vector<uint8_t> pbkdf2HmacSha1(const std::vector<uint8_t>& password, const std::vector<uint8_t>& salt, int iterations, int digestSize);
+
+    // Random stuff
+    std::vector<uint8_t> generateVectorWithRandomData(size_t length);
+};
 
-#ifdef BELL_USE_MBEDTLS
-#include "CryptoMbedTLS.h"
-#define Crypto CryptoMbedTLS
-#else
-#include "CryptoOpenSSL.h"
-#define Crypto CryptoOpenSSL
-#endif
 #endif

+ 0 - 78
components/spotify/cspot/bell/include/CryptoMbedTLS.h

@@ -1,78 +0,0 @@
-#ifndef BELL_CRYPTOMBEDTLS_H
-#define BELL_CRYPTOMBEDTLS_H
-
-#ifdef BELL_USE_MBEDTLS
-#include <vector>
-#include <string>
-#include <memory>
-
-#include <mbedtls/base64.h>
-#include <mbedtls/bignum.h>
-#include <mbedtls/md.h>
-#include <mbedtls/aes.h>
-#include <mbedtls/pkcs5.h>
-#include <mbedtls/entropy.h>
-#include <mbedtls/ctr_drbg.h>
-
-
-#define DH_KEY_SIZE 96
-
-static unsigned char DHPrime[] = {
-    /* Well-known Group 1, 768-bit prime */
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
-    0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
-    0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e,
-    0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
-    0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
-    0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a,
-    0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14,
-    0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
-    0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4,
-    0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff
-};
-
-static unsigned char DHGenerator[1] = {2};
-
-class CryptoMbedTLS {
-private:
-    mbedtls_md_context_t sha1Context;
-    mbedtls_aes_context aesCtx;
-public:
-    CryptoMbedTLS();
-    ~CryptoMbedTLS();
-    // Base64
-    std::vector<uint8_t> base64Decode(const std::string& data);
-    std::string base64Encode(const std::vector<uint8_t>& data);
-
-    // Sha1
-    void sha1Init();
-    void sha1Update(const std::string& s);
-    void sha1Update(const std::vector<uint8_t>& vec);
-    std::string sha1Final();
-    std::vector<uint8_t> sha1FinalBytes();
-
-    // HMAC SHA1
-    std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
-
-    // AES CTR
-    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* data, size_t nbytes);
-    
-    // AES ECB
-    void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);
-
-    // Diffie Hellman
-    std::vector<uint8_t> publicKey;
-    std::vector<uint8_t> privateKey;
-    void dhInit();
-    std::vector<uint8_t> dhCalculateShared(const std::vector<uint8_t>& remoteKey);
-
-    // PBKDF2
-    std::vector<uint8_t> pbkdf2HmacSha1(const std::vector<uint8_t>& password, const std::vector<uint8_t>& salt, int iterations, int digestSize);
-
-    // Random stuff
-    std::vector<uint8_t> generateVectorWithRandomData(size_t length);
-};
-
-#endif
-#endif

+ 0 - 84
components/spotify/cspot/bell/include/CryptoOpenSSL.h

@@ -1,84 +0,0 @@
-#ifndef BELL_CRYPTOOPENSSL_H
-#define BELL_CRYPTOOPENSSL_H
-
-#include <vector>
-#include <string>
-#include <memory>
-#include <openssl/engine.h>
-#include <openssl/rand.h>
-#include <openssl/err.h>
-#include <openssl/dh.h>
-#include <openssl/bn.h>
-#include <openssl/bio.h>
-#include <openssl/evp.h>
-#include <openssl/sha.h>
-#include <openssl/hmac.h>
-#include <openssl/aes.h>
-#include <openssl/modes.h>
-
-#define DH_KEY_SIZE 96
-
-static unsigned char DHPrime[] = {
-    /* Well-known Group 1, 768-bit prime */
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9,
-    0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6,
-    0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e,
-    0x08, 0x8a, 0x67, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6,
-    0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
-    0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a,
-    0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14,
-    0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45,
-    0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4,
-    0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff
-};
-
-static unsigned char DHGenerator[1] = {2};
-
-class CryptoOpenSSL {
-private:
-    DH* dhContext = nullptr;
-    SHA_CTX sha1Context;
-public:
-    CryptoOpenSSL();
-    ~CryptoOpenSSL();
-    // Base64
-    std::vector<uint8_t> base64Decode(const std::string& data);
-    std::string base64Encode(const std::vector<uint8_t>& data);
-
-    // Sha1
-    void sha1Init();
-    void sha1Update(const std::string& s);
-    void sha1Update(const std::vector<uint8_t>& vec);
-
-    void connectSSL(std::string url);
-    int readSSL(uint8_t* buf, int len);
-    int writeSSL(uint8_t* buf, int len);
-    void closeSSL();
-    
-    std::string sha1Final();
-    std::vector<uint8_t> sha1FinalBytes();
-
-    // HMAC SHA1
-    std::vector<uint8_t> sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message);
-
-    // AES CTR
-    void aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* buffer, size_t nbytes);
-    
-    // AES ECB
-    void aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data);
-
-    // Diffie Hellman
-    std::vector<uint8_t> publicKey;
-    std::vector<uint8_t> privateKey;
-    void dhInit();
-    std::vector<uint8_t> dhCalculateShared(const std::vector<uint8_t>& remoteKey);
-
-    // PBKDF2
-    std::vector<uint8_t> pbkdf2HmacSha1(const std::vector<uint8_t>& password, const std::vector<uint8_t>& salt, int iterations, int digestSize);
-
-    // Random stuff
-    std::vector<uint8_t> generateVectorWithRandomData(size_t length);
-};
-
-#endif

+ 1 - 1
components/spotify/cspot/bell/include/HTTPClient.h

@@ -4,7 +4,7 @@
 #include "BellSocket.h"
 #include "ByteStream.h"
 #include "TCPSocket.h"
-#include "platform/TLSSocket.h"
+#include "TLSSocket.h"
 #include <map>
 #include <memory>
 #include <string>

+ 11 - 6
components/spotify/cspot/bell/include/HTTPServer.h

@@ -11,16 +11,21 @@
 #include <iostream>
 #include <queue>
 #include <stdio.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include "win32shim.h"
+#else
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include <unistd.h>
+#endif
 #include <sstream>
 #include <BellLogger.h>
-#include <sys/select.h>
 #include <sys/types.h>
-#include <unistd.h>
 #include <fstream>
-#include <sys/socket.h>
 #include <string>
-#include <netdb.h>
 #include <mutex>
 #include <fcntl.h>
 #include "BaseHTTPServer.h"
@@ -46,7 +51,7 @@ namespace bell
         std::map<int, HTTPConnection> connections;
         void writeResponse(const HTTPResponse &);
         void writeResponseEvents(int connFd);
-        void findAndHandleRoute(std::string &, std::string &, int connectionFd);
+        void findAndHandleRoute(HTTPConnection& connection);
 
         std::vector<std::string> splitUrl(const std::string &url, char delimiter);
         std::mutex responseMutex;
@@ -60,7 +65,7 @@ namespace bell
     public:
         HTTPServer(int serverPort);
 
-        void registerHandler(RequestType requestType, const std::string &, httpHandler);
+        void registerHandler(RequestType requestType, const std::string &, httpHandler, bool readDataToStr = false);
         void respond(const HTTPResponse &);
         void redirectTo(const std::string&, int connectionFd);
         void publishEvent(std::string eventName, std::string eventData);

+ 1 - 1
components/spotify/cspot/bell/include/HTTPStream.h

@@ -6,7 +6,7 @@
 #include <ByteStream.h>
 #include <BellSocket.h>
 #include <TCPSocket.h>
-#include <platform/TLSSocket.h>
+#include <TLSSocket.h>
 
 /*
 * HTTPStream

+ 20 - 9
components/spotify/cspot/bell/include/TCPSocket.h

@@ -9,15 +9,21 @@
 #include <cstring>
 #include <stdlib.h>
 #include <sys/types.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include "win32shim.h"
+#else
 #include <sys/socket.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <unistd.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#endif
 #include <sstream>
 #include <fstream>
-#include <netinet/tcp.h>
 #include <BellLogger.h>
-#include <sys/ioctl.h>
 
 namespace bell
 {
@@ -77,18 +83,23 @@ namespace bell
         }
 
         size_t read(uint8_t *buf, size_t len) {
-            return recv(sockFd, buf, len, 0);
+            return recv(sockFd, (char*) buf, len, 0);
         }
 
         size_t write(uint8_t *buf, size_t len) {
-            return send(sockFd, buf, len, 0);
+            return send(sockFd, (char*) buf, len, 0);
         }
 
-        size_t poll() {
-            int value;
-            ioctl(sockFd, FIONREAD, &value);
-            return value;
-        }
+		size_t poll() {
+#ifdef _WIN32
+            unsigned long value;
+			ioctlsocket(sockFd, FIONREAD, &value);
+#else
+			int value;
+			ioctl(sockFd, FIONREAD, &value);
+#endif
+			return value;
+		}
 
         void close() {
             if (!isClosed) {

+ 7 - 21
components/spotify/cspot/bell/include/platform/TLSSocket.h → components/spotify/cspot/bell/include/TLSSocket.h

@@ -8,50 +8,36 @@
 #include <fstream>
 #include <iostream>
 #include <memory>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
 #include <netdb.h>
 #include <netinet/in.h>
 #include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
 #include <sstream>
 #include <stdlib.h>
 #include <string>
-#include <sys/socket.h>
 #include <sys/types.h>
-#include <unistd.h>
 #include <vector>
 
-#ifdef BELL_USE_MBEDTLS
-
 #include "mbedtls/net_sockets.h"
 #include "mbedtls/ssl.h"
 #include "mbedtls/entropy.h"
 #include "mbedtls/ctr_drbg.h"
 #include "mbedtls/debug.h"
-#else
-#include <openssl/bio.h>
-#include <openssl/err.h>
-#include <openssl/ssl.h>
-#endif
 
 namespace bell {
 class TLSSocket : public bell::Socket {
 private:
-#ifdef BELL_USE_MBEDTLS
     mbedtls_net_context server_fd;
     mbedtls_entropy_context entropy;
     mbedtls_ctr_drbg_context ctr_drbg;
     mbedtls_ssl_context ssl;
     mbedtls_ssl_config conf;
-#else
-
-  BIO *sbio, *out;
-  int len;
-  char tmpbuf[1024];
-  SSL_CTX *ctx;
-  SSL *ssl;
-
-  int sockFd;
-  int sslFd;
-#endif
 
   bool isClosed = false;
 public:

+ 8 - 0
components/spotify/cspot/bell/include/TimeDefs.h

@@ -0,0 +1,8 @@
+//
+// Created by Filip Grzywok on 28/02/2022.
+//
+
+#ifndef EUPHONIUMCLI_TIMEDEFS_H
+#define EUPHONIUMCLI_TIMEDEFS_H
+
+#endif // EUPHONIUMCLI_TIMEDEFS_H

+ 19 - 0
components/spotify/cspot/bell/include/audio/codec/AACDecoder.h

@@ -0,0 +1,19 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#pragma once
+
+#include "BaseCodec.h"
+#include "aacdec.h"
+
+class AACDecoder : public BaseCodec {
+  private:
+	HAACDecoder aac;
+	int16_t *pcmData;
+	AACFrameInfo frame = {};
+
+  public:
+	AACDecoder();
+	~AACDecoder();
+	bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override;
+	uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) override;
+};

+ 18 - 0
components/spotify/cspot/bell/include/audio/codec/ALACDecoder.h

@@ -0,0 +1,18 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#pragma once
+
+#include "BaseCodec.h"
+#include "alac_wrapper.h"
+
+class ALACDecoder : public BaseCodec {
+  private:
+	alac_codec_s* alacCodec;
+	int16_t *pcmData;
+
+  public:
+	ALACDecoder();
+	~ALACDecoder();
+	bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override;
+	uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) override;
+};

+ 23 - 0
components/spotify/cspot/bell/include/audio/codec/AudioCodecs.h

@@ -0,0 +1,23 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#pragma once
+
+#include "BaseCodec.h"
+#include "BaseContainer.h"
+#include <memory>
+
+enum class AudioCodec {
+	UNKNOWN = 0,
+	AAC = 1,
+	MP3 = 2,
+	VORBIS = 3,
+	OPUS = 4,
+	FLAC = 5,
+};
+
+class AudioCodecs {
+  public:
+	static std::shared_ptr<BaseCodec> getCodec(AudioCodec type);
+	static std::shared_ptr<BaseCodec> getCodec(BaseContainer *container);
+	static void addCodec(AudioCodec type, const std::shared_ptr<BaseCodec> &codec);
+};

+ 39 - 0
components/spotify/cspot/bell/include/audio/codec/BaseCodec.h

@@ -0,0 +1,39 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#pragma once
+
+#include "BaseContainer.h"
+
+class BaseCodec {
+  public:
+	/**
+	 * Setup the codec (sample rate, channel count, etc) using the specified container.
+	 */
+	virtual bool setup(BaseContainer *container);
+	/**
+	 * Setup the codec manually, using the provided values.
+	 */
+	virtual bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) = 0;
+	/**
+	 * Decode the given sample.
+	 *
+	 * @param [in] inData encoded data. Should allow nullptr, in which case nullptr should be returned.
+	 * @param [in] inLen size of inData, in bytes
+	 * @param [out] outLen size of output PCM data, in bytes
+	 * @return pointer to decoded raw PCM audio data, allocated inside the codec object; nullptr on failure
+	 */
+	virtual uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) = 0;
+	/**
+	 * Read a single sample from the container, decode it, and return the result.
+	 *
+	 * @param [in] container media container to read the sample from (the container's codec must match this instance)
+	 * @param [out] outLen size of output PCM data, in bytes
+	 * @return pointer to decoded raw PCM audio data, allocated inside the codec object; nullptr on failure
+	 */
+	uint8_t *decode(BaseContainer *container, uint32_t &outLen);
+	/**
+	 * Last error that occurred, this is a codec-specific value.
+	 * This may be set by a codec upon decoding failure.
+	 */
+	int lastErrno = -1;
+};

+ 1 - 1
components/spotify/cspot/bell/include/DecoderGlobals.h → components/spotify/cspot/bell/include/audio/codec/DecoderGlobals.h

@@ -43,7 +43,7 @@ namespace bell
         }
     };
 
-    extern std::shared_ptr<bell::DecodersInstance> decodersInstance;
+    extern bell::DecodersInstance* decodersInstance;
 
     void createDecoders();
 }

+ 19 - 0
components/spotify/cspot/bell/include/audio/codec/MP3Decoder.h

@@ -0,0 +1,19 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#pragma once
+
+#include "BaseCodec.h"
+#include "mp3dec.h"
+
+class MP3Decoder : public BaseCodec {
+  private:
+	HMP3Decoder mp3;
+	int16_t *pcmData;
+	MP3FrameInfo frame = {};
+
+  public:
+	MP3Decoder();
+	~MP3Decoder();
+	bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override;
+	uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) override;
+};

+ 19 - 0
components/spotify/cspot/bell/include/audio/codec/OPUSDecoder.h

@@ -0,0 +1,19 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#pragma once
+
+#include "BaseCodec.h"
+
+struct OpusDecoder;
+
+class OPUSDecoder : public BaseCodec {
+  private:
+	OpusDecoder *opus;
+	int16_t *pcmData;
+
+  public:
+	OPUSDecoder();
+	~OPUSDecoder();
+	bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override;
+	uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) override;
+};

+ 25 - 0
components/spotify/cspot/bell/include/audio/codec/VorbisDecoder.h

@@ -0,0 +1,25 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#pragma once
+
+#include "BaseCodec.h"
+#include "ivorbiscodec.h"
+
+class VorbisDecoder : public BaseCodec {
+  private:
+	vorbis_info *vi = nullptr;
+	vorbis_comment *vc = nullptr;
+	vorbis_dsp_state *vd = nullptr;
+	ogg_packet op = {};
+	int16_t *pcmData;
+
+  public:
+	VorbisDecoder();
+	~VorbisDecoder();
+	bool setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) override;
+	uint8_t *decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) override;
+	bool setup(BaseContainer *container) override;
+
+  private:
+	void setPacket(uint8_t *inData, uint32_t inLen) const;
+};

+ 10 - 0
components/spotify/cspot/bell/include/audio/container/AudioContainers.h

@@ -0,0 +1,10 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-15.
+
+#pragma once
+
+#include "BaseContainer.h"
+
+class AudioContainers {
+  public:
+	static std::unique_ptr<BaseContainer> create(const char *mimeType);
+};

+ 104 - 0
components/spotify/cspot/bell/include/audio/container/BaseContainer.h

@@ -0,0 +1,104 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-7.
+
+#pragma once
+
+#include "BinaryReader.h"
+#include "ByteStream.h"
+#include <cstdint>
+#include <memory>
+
+/**
+ * Either the media file or the requested position/offset is not loaded yet.
+ */
+#define SAMPLE_NOT_LOADED	-1
+/**
+ * The media file does not contain the requested position/offset.
+ */
+#define SAMPLE_NOT_FOUND	-2
+/**
+ * The file is not seekable (i.e. doesn't contain an index table).
+ */
+#define SAMPLE_NOT_SEEKABLE -3
+
+enum class AudioCodec;
+
+class BaseContainer {
+  public:
+	BaseContainer() = default;
+	/**
+	 * Feed a new data source to the container.
+	 * @param stream ByteStream reading source data
+	 * @param position absolute position of the current ByteStream within the source media
+	 */
+	virtual void feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position);
+	/**
+	 * Try to parse the media provided by the source stream.
+	 * @return whether parsing was successful
+	 */
+	virtual bool parse() = 0;
+	/**
+	 * Get absolute offset within the source media for the given timestamp.
+	 * When seeking to a specified time, the caller should run feed() with a stream
+	 * reader starting at the returned offset. Depending on the container type,
+	 * the returned offset may not point to the exact time position (i.e. chunks with
+	 * headers), so seekTo() should be used afterwards.
+	 *
+	 * @param timeMs requested timestamp, in milliseconds
+	 * @return byte offset within the source media that should be loaded
+	 * in order to seek to the requested position; negative value on error
+	 */
+	virtual int32_t getLoadingOffset(uint32_t timeMs) = 0;
+	/**
+	 * Try to seek to the specified position (in milliseconds), using the currently
+	 * loaded source stream. This method will fail if the source stream does not yield
+	 * data for the requested position, or block until the stream loads data for this position.
+	 *
+	 * @param timeMs requested timestamp, in milliseconds
+	 */
+	virtual bool seekTo(uint32_t timeMs) = 0;
+	/**
+	 * Get the current playback position, in milliseconds. May return -1 if the track
+	 * is not playing (has ended or not started yet).
+	 */
+	virtual int32_t getCurrentTimeMs() = 0;
+	/**
+	 * Read an encoded audio sample from the container, starting at the current position.
+	 *
+	 * @param [out] len length of the data stored in the returned pointer, in bytes
+	 * @return pointer to data allocated inside the container object; should not be freed or changed.
+	 * On failure, nullptr is returned, and len is left unchanged.
+	 */
+	virtual uint8_t *readSample(uint32_t &len) = 0;
+	/**
+	 * Get optional initialization data for the specified codec. This may be used by a codec,
+	 * for containers that contain the setup data.
+	 *
+	 * @param [out] len length of the setup data
+	 * @return ptr to [len] setup data bytes, or nullptr if not available/not supported
+	 */
+	virtual uint8_t *getSetupData(uint32_t &len, AudioCodec matchCodec) = 0;
+
+  public:
+	bool closed = false;
+	bool isSeekable = false;
+	// audio parameters
+	AudioCodec codec = (AudioCodec)0;
+	uint32_t sampleRate = 0;
+	uint8_t channelCount = 0;
+	uint8_t bitDepth = 0;
+	uint32_t durationMs = 0;
+
+  protected:
+	std::unique_ptr<bell::BinaryReader> reader;
+	std::shared_ptr<bell::ByteStream> source;
+	uint32_t pos = 0;
+	uint8_t readUint8();
+	uint16_t readUint16();
+	uint32_t readUint24();
+	uint32_t readUint32();
+	uint64_t readUint64();
+	uint32_t readVarint32();
+	uint32_t readBytes(uint8_t *dst, uint32_t num);
+	uint32_t skipBytes(uint32_t num);
+	uint32_t skipTo(uint32_t offset);
+};

+ 108 - 0
components/spotify/cspot/bell/include/audio/container/Mpeg4Atoms.h

@@ -0,0 +1,108 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-8.
+
+#pragma once
+
+#include <cstdint>
+
+enum class AudioSampleFormat;
+enum class MP4AObjectType;
+enum class MP4AProfile;
+
+typedef struct {
+	/** Absolute offset of mdat header (or moof for fMP4) */
+	uint32_t start;
+	/** Absolute offset of the last mdat byte */
+	uint32_t end;
+	/** Total duration of this fragment */
+	uint32_t duration;
+} Mpeg4Fragment;
+
+typedef struct {
+	/** Number of chunks this descriptor applies to */
+	uint16_t count;
+	/** Number of samples in the described chunks */
+	uint32_t samples;
+	uint16_t sampleDescriptionId;
+} Mpeg4ChunkRange;
+
+/** Absolute offset of the chunk data */
+typedef uint32_t Mpeg4ChunkOffset;
+
+typedef struct {
+	/** Abs. offset of data start in the current chunk */
+	uint32_t start;
+	/** Abs. offset of data end in the current chunk */
+	uint32_t end;
+	/** Abs. offset of the next chunk data, or 0 for last chunk in a fragment */
+	uint32_t nextStart;
+} Mpeg4Chunk;
+
+typedef struct {
+	/** Number of samples this descriptor applies to */
+	uint32_t count;
+	/** Duration of the described samples */
+	uint32_t duration;
+} Mpeg4SampleRange;
+
+/** Size of a single sample */
+typedef uint32_t Mpeg4SampleSize;
+
+/** Flags for a sample */
+typedef uint32_t SampleFlags;
+
+/** Default values for samples in the movie/fragment */
+typedef struct {
+	/** Absolute offset of first mdat byte */
+	uint32_t offset;
+	uint32_t sampleDescriptionId;
+	uint32_t duration;
+	uint32_t size;
+	SampleFlags flags;
+} SampleDefaults;
+
+/** Sample Description Table */
+typedef struct {
+	uint16_t dataReferenceIndex;
+	AudioSampleFormat format;
+	// params for MPEG-4 Elementary Stream Descriptors
+	MP4AObjectType mp4aObjectType;
+	MP4AProfile mp4aProfile;
+	// atom header for unknown descriptors
+	uint32_t dataType;
+	// codec-specific data (either DecoderSpecificInfo or the entire descriptor)
+	uint32_t dataLength;
+	uint8_t *data;
+} SampleDescription;
+
+typedef struct {
+	// byte 1 - bits 0:7
+	bool durationIsEmpty : 1;
+	bool defaultBaseIsMoof : 1;
+	bool dummy1 : 6;
+	// byte 2 - bits 0:7
+	uint8_t dummy2 : 8;
+	// byte 3 - bits 0:7
+	bool baseDataOffsetPresent : 1;
+	bool sampleDescriptionIndexPresent : 1;
+	bool dummy3 : 1;
+	bool defaultSampleDurationPresent : 1;
+	bool defaultSampleSizePresent : 1;
+	bool defaultSampleFlagsPresent : 1;
+	bool dummy4 : 2;
+} TfFlags;
+
+typedef struct {
+	// byte 1 - bits 0:7
+	uint8_t dummy1 : 8;
+	// byte 2 - bits 0:7
+	bool sampleDurationPresent : 1;
+	bool sampleSizePresent : 1;
+	bool sampleFlagsPresent : 1;
+	bool sampleCompositionTimeOffsetsPresent : 1;
+	bool dummy2 : 4;
+	// byte 3 - bits 0:7
+	bool dataOffsetPresent : 1;
+	bool dummy3 : 1;
+	bool firstSampleFlagsPresent : 1;
+	bool dummy4 : 5;
+} TrFlags;

+ 114 - 0
components/spotify/cspot/bell/include/audio/container/Mpeg4Container.h

@@ -0,0 +1,114 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-8.
+
+#pragma once
+
+#include "BaseContainer.h"
+#include "Mpeg4Atoms.h"
+#include <memory>
+
+class Mpeg4Container : public BaseContainer {
+  public:
+	~Mpeg4Container();
+	/**
+	 * Start parsing the MP4 file. This method expects the source to read from 0th byte.
+	 * This method leaves pos at first mdat data byte, or mdat header for fMP4 files.
+	 */
+	bool parse() override;
+	int32_t getLoadingOffset(uint32_t timeMs) override;
+	bool seekTo(uint32_t timeMs) override;
+	int32_t getCurrentTimeMs() override;
+	uint8_t *readSample(uint32_t &len) override;
+	uint8_t *getSetupData(uint32_t &len, AudioCodec matchCodec) override;
+	void feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) override;
+
+  private:
+	/**
+	 * Parse a single movie fragment. This method expects the source to read moof data, without the header.
+	 * After running, [pos] is left at next mdat header. A new fragment will be created if [pos] does not exist
+	 * in [fragments] table.
+	 */
+	bool parseMoof(uint32_t moofSize);
+	bool goToData();
+	// char mediaBrand[5];
+	uint32_t totalDuration;
+	bool totalDurationPresent = false;
+	int8_t audioTrackId = -1;
+	uint32_t timescale = 0;
+	uint32_t sampleSizeMax = 0;
+	uint8_t *sampleData = nullptr;
+	uint32_t sampleDataLen = 0;
+	bool isParsed = false;
+	bool isFragmented = false;
+	/** True if source reads **audio** mdat data bytes, false if source reads atom headers */
+	bool isInData = false;
+
+  private: // data for the entire movie:
+	/** All fragments in the MPEG file */
+	Mpeg4Fragment *fragments;
+	uint16_t fragmentsLen;
+	/** Default sample descriptions for each track */
+	SampleDefaults *sampleDefs;
+	uint32_t sampleDefsLen;
+	/** Track IDs of [sampleDef] items */
+	uint32_t *sampleDefTracks;
+	/** Sample Description Table */
+	SampleDescription *sampleDesc;
+	uint32_t sampleDescLen;
+
+  private: // data changing every fragment:
+	/** Chunks in the current fragment */
+	Mpeg4ChunkRange *chunks;
+	uint32_t chunksLen;
+	/** Absolute chunk offsets in the current fragment */
+	Mpeg4ChunkOffset *chunkOffsets;
+	uint32_t chunkOffsetsLen;
+	/** All sample descriptors in the current fragment */
+	Mpeg4SampleRange *samples;
+	uint32_t samplesLen;
+	/** All sample sizes in the current fragment */
+	Mpeg4SampleSize *sampleSizes;
+	uint32_t sampleSizesLen;
+
+  private: // current status and position within the file
+	/** Currently loaded fragment (ptr) */
+	Mpeg4Fragment *curFragment;
+	/** The chunk currently being processed */
+	Mpeg4Chunk curChunk;
+	/** Size of the current sample (ptr) */
+	Mpeg4SampleSize *curSampleSize;
+
+  private: // Mpeg4Utils.cpp
+	void readAtomHeader(uint32_t &size, uint32_t &type);
+	void freeAll();
+	void freeFragment();
+	SampleDefaults *getSampleDef(uint32_t trackId);
+	void setCurrentFragment();
+	void setCurrentSample();
+	static bool isInFragment(Mpeg4Fragment *f, uint32_t offset);
+	static AudioCodec getCodec(SampleDescription *desc);
+	Mpeg4Fragment *createFragment();
+	int64_t findSample(int64_t byTime, int32_t byPos, uint64_t startTime);
+
+  private: // Mpeg4Parser.cpp
+	/** Populate [chunks] using the Sample-to-chunk Table */
+	void readStsc();
+	/** Populate [chunkOffsets] using the Chunk Offset Table */
+	void readStco();
+	/** Populate [samples] using the Time-to-sample Table */
+	void readStts();
+	/** Populate [sampleSizes] using the Sample Size Table */
+	void readStsz();
+	/** Populate [sampleDesc] using the Sample Description Table */
+	void readStsd();
+
+  private: // Mpeg4ParserFrag.cpp
+	/** Populate [fragments] using the Segment Index Table */
+	void readSidx(uint32_t atomSize);
+	/** Populate [sampleDefs] using Track Extends */
+	void readTrex();
+	/** Populate [sampleDefs] using Track Fragment Header */
+	void readTfhd(uint32_t trafEnd, uint32_t moofOffset);
+	/** Populate [chunks, chunkOffsets, samples, sampleSizes] using Track Fragment Run Table */
+	void readTrun(uint32_t atomSize, uint32_t moofOffset);
+	void allocSampleData();
+};

+ 206 - 0
components/spotify/cspot/bell/include/audio/container/Mpeg4Types.h

@@ -0,0 +1,206 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#pragma once
+
+enum class AtomType {
+	/** File Type */
+	ATOM_FTYP = 0x66747970,
+	/** Movie */
+	ATOM_MOOV = 0x6D6F6F76,
+	/** Movie Header */
+	ATOM_MVHD = 0x6D766864,
+	/** Movie Extends */
+	ATOM_MVEX = 0x6D766578,
+	/** Movie Extends Header */
+	ATOM_MEHD = 0x6D656864,
+	/** Track Extends */
+	ATOM_TREX = 0x74726578,
+	/** Track */
+	ATOM_TRAK = 0x7472616B,
+	/** Track Header */
+	ATOM_TKHD = 0x746B6864,
+	/** Edit */
+	ATOM_EDTS = 0x65647473,
+	/** Edit List */
+	ATOM_ELST = 0x656C7374,
+	/** Media */
+	ATOM_MDIA = 0x6D646961,
+	/** Media Header */
+	ATOM_MDHD = 0x6D646864,
+	/** Handler Reference */
+	ATOM_HDLR = 0x68646C72,
+	/** Handler Type - Sound */
+	ATOM_SOUN = 0x736F756E,
+	/** Handler Type - Video */
+	ATOM_VIDE = 0x76696465,
+	/** Handler Type - Subtitle */
+	ATOM_SUBT = 0x73756274,
+	/** Media Information */
+	ATOM_MINF = 0x6D696E66,
+	/** Data Information */
+	ATOM_DINF = 0x64696E66,
+	/** Data Reference */
+	ATOM_DREF = 0x64726566,
+	/** Data Entry Url */
+	ATOM_URL = 0x75726C20,
+	/** Sample Table */
+	ATOM_STBL = 0x7374626C,
+	/** Sample Description */
+	ATOM_STSD = 0x73747364,
+	/** siDecompressionParam */
+	ATOM_WAVE = 0x77617665,
+	/** Format Atom */
+	ATOM_FRMA = 0x66726D61,
+	/** Audio Channel Layout Atom */
+	ATOM_CHAN = 0x6368616E,
+	/** Terminator Atom */
+	ATOM_TERM = 0x00000000,
+	/** MPEG-4 Elementary Stream Descriptor */
+	ATOM_ESDS = 0x65736473,
+	/** Time-to-sample Table */
+	ATOM_STTS = 0x73747473,
+	/** Sync Sample Table */
+	ATOM_STSS = 0x73747373,
+	/** Sample-to-chunk Table */
+	ATOM_STSC = 0x73747363,
+	/** Chunk Offset Table */
+	ATOM_STCO = 0x7374636F,
+	/** Sample Size Table */
+	ATOM_STSZ = 0x7374737A,
+	/** Sound Media Header */
+	ATOM_SMHD = 0x736D6864,
+	/** Segment Index Table */
+	ATOM_SIDX = 0x73696478,
+	/** Movie Fragment */
+	ATOM_MOOF = 0x6D6F6F66,
+	/** Movie Fragment Header */
+	ATOM_MFHD = 0x6D666864,
+	/** Track Fragment */
+	ATOM_TRAF = 0x74726166,
+	/** Track Fragment Header */
+	ATOM_TFHD = 0x74666864,
+	/** Track Fragment Run */
+	ATOM_TRUN = 0x7472756E,
+	/** Media Data */
+	ATOM_MDAT = 0x6D646174,
+};
+
+// These formats are the direct sub-children of the stsd atom.
+// https://mp4ra.org/#/codecs (+additions)
+enum class AudioSampleFormat {
+	UNDEFINED = 0,
+	A3DS = 0x61336473,		// Auro-Cx 3D audio
+	AC3 = 0x61632d33,		// AC-3 audio
+	AC4 = 0x61632d34,		// AC-4 audio
+	AGSM = 0x6167736d,		// GSM
+	ALAC = 0x616c6163,		// Apple lossless audio codec
+	ALAW = 0x616c6177,		// a-Law
+	CAVS = 0x63617673,		// AVS2-P3 codec
+	DRA1 = 0x64726131,		// DRA Audio
+	DTS_MINUS = 0x6474732d, // Dependent base layer for DTS layered audio
+	DTS_PLUS = 0x6474732b,	// Enhancement layer for DTS layered audio
+	DTSC = 0x64747363,		// Core Substream
+	DTSE = 0x64747365,		// Extension Substream containing only LBR
+	DTSH = 0x64747368,		// Core Substream + Extension Substream
+	DTSL = 0x6474736c,		// Extension Substream containing only XLL
+	DTSX = 0x64747378,		// DTS-UHD profile 2
+	DTSY = 0x64747379,		// DTS-UHD profile 3 or higher
+	DVI = 0x64766920,		// DVI (as used in RTP, 4:1 compression)
+	EC3 = 0x65632d33,		// Enhanced AC-3 audio
+	ENCA = 0x656e6361,		// Encrypted/Protected audio
+	FL32 = 0x666c3332,		// 32 bit float
+	FL64 = 0x666c3634,		// 64 bit float
+	FLAC = 0x664c6143,		// Free Lossless Audio Codec
+	G719 = 0x67373139,		// ITU-T Recommendation G.719 (2008)
+	G726 = 0x67373236,		// ITU-T Recommendation G.726 (1990)
+	IMA4 = 0x696d6134,		// IMA (International Multimedia Assocation, defunct, 4:1)
+	IN24 = 0x696e3234,		// 24 bit integer uncompressed
+	IN32 = 0x696e3332,		// 32 bit integer uncompressed
+	LPCM = 0x6c70636d,		// Uncompressed audio (various integer and float formats)
+	M4AE = 0x6d346165,		// MPEG-4 Audio Enhancement
+	MHA1 = 0x6d686131,		// MPEG-H Audio (single stream, unencapsulated)
+	MHA2 = 0x6d686132,		// MPEG-H Audio (multi-stream, unencapsulated)
+	MHM1 = 0x6d686d31,		// MPEG-H Audio (single stream, MHAS encapsulated)
+	MHM2 = 0x6d686d32,		// MPEG-H Audio (multi-stream, MHAS encapsulated)
+	MLPA = 0x6d6c7061,		// MLP Audio
+	MP4A = 0x6d703461,		// MPEG-4 Audio
+	OPUS = 0x4f707573,		// Opus audio coding
+	QCLP = 0x51636c70,		// Qualcomm PureVoice
+	QDM2 = 0x51444d32,		// Qdesign music 2
+	QDMC = 0x51444d43,		// Qdesign music 1
+	RAW = 0x72617720,		// Uncompressed audio
+	SAMR = 0x73616d72,		// Narrowband AMR voice
+	SAWB = 0x73617762,		// Wideband AMR voice
+	SAWP = 0x73617770,		// Extended AMR-WB (AMR-WB+)
+	SEVC = 0x73657663,		// EVRC Voice
+	SEVS = 0x73657673,		// Enhanced Voice Services (EVS)
+	SQCP = 0x73716370,		// 13K Voice
+	SSMV = 0x73736d76,		// SMV Voice
+	TWOS = 0x74776f73,		// Uncompressed 16-bit audio
+	ULAW = 0x756c6177,		// Samples have been compressed using uLaw 2:1.
+	VDVA = 0x76647661,		// DV audio (variable duration per video frame)
+};
+
+// These are present in the DecoderConfigDescriptor tag in ESDS (for AudioSampleFormat::FORMAT_MP4A).
+// Source: https://mp4ra.org/#/codecs
+enum class MP4AObjectType {
+	UNDEFINED = 0,
+	_13K = 0xE1,		 // 13K Voice
+	AAC_LC = 0x67,		 // ISO/IEC 13818-7 (AAC) Low Complexity Profile
+	AAC_MAIN = 0x66,	 // ISO/IEC 13818-7 (AAC) Main Profile
+	AAC_SSR = 0x68,		 // ISO/IEC 13818-7 (AAC) Scaleable Sampling Rate Profile
+	AC3 = 0xA5,			 // AC-3
+	AC3_ENH = 0xA6,		 // Enhanced AC-3
+	AC4 = 0xAE,			 // AC-4
+	AURO_CX_3D = 0xAF,	 // Auro-Cx 3D audio
+	DRA = 0xA7,			 // DRA Audio
+	DTS_CORE = 0xA9,	 // Core Substream
+	DTS_CORE_EXT = 0xAA, // Core Substream + Extension Substream
+	DTS_LBR = 0xAC,		 // Extension Substream containing only LBR
+	DTS_UHD2 = 0xB2,	 // DTS-UHD profile 2
+	DTS_UHD3 = 0xB3,	 // DTS-UHD profile 3 or higher
+	DTS_XLL = 0xAB,		 // Extension Substream containing only XLL
+	EVRC = 0xA0,		 // EVRC Voice
+	G719 = 0xA8,		 // ITU G.719 Audio
+	MP4A = 0x40,		 // ISO/IEC 14496-3 (MPEG-4 Audio)
+	MPEG1 = 0x6B,		 // ISO/IEC 11172-3 (MPEG-1 Part 3)
+	MPEG2 = 0x69,		 // ISO/IEC 13818-3 (MPEG-2 Part 3)
+	OPUS = 0xAD,		 // Opus audio
+	SMV = 0xA1,			 // SMV Voice
+	VORBIS = 0xDD,		 // Vorbis
+};
+
+// These are present in the DecoderSpecificInfo tag in ESDS (for MP4AObjectType::TYPE_MP4A).
+// Source: https://wiki.multimedia.cx/index.php/MPEG-4_Audio
+enum class MP4AProfile {
+	UNDEFINED = 0,
+	AAC_MAIN = 1,							 // AAC main
+	AAC_LC = 2,								 // AAC LC
+	AAC_SSR = 3,							 // AAC SSR
+	AAC_LTP = 4,							 // AAC LTP
+	SBR = 5,								 // SBR
+	AAC_SCALABLE = 6,						 // AAC Scalable
+	TWINVQ = 7,								 // TwinVQ
+	CELP = 8,								 // CELP
+	HVXC = 9,								 // HVXC
+	TTSI = 12,								 // TTSI
+	MAIN_SYNTHETIC = 13,					 // Main synthetic
+	WAVETABLE_SYNTHESIS = 14,				 // Wavetable synthesis
+	GENERAL_MIDI = 15,						 // General MIDI
+	ALGORITHMIC_SYNTHESIS_AND_AUDIO_FX = 16, // Algorithmic Synthesis and Audio FX
+	ER_AAC_LC = 17,							 // ER AAC LC
+	ER_AAC_LTP = 19,						 // ER AAC LTP
+	ER_AAC_SCALABLE = 20,					 // ER AAC Scalable
+	ER_TWINVQ = 21,							 // ER TwinVQ
+	ER_BSAC = 22,							 // ER BSAC
+	ER_AAC_LD = 23,							 // ER AAC LD
+	ER_CELP = 24,							 // ER CELP
+	ER_HVXC = 25,							 // ER HVXC
+	ER_HILN = 26,							 // ER HILN
+	ER_PARAMETRIC = 27,						 // ER Parametric
+	SSC = 28,								 // SSC
+	LAYER_1 = 32,							 // Layer-1
+	LAYER_2 = 33,							 // Layer-2
+	LAYER_3 = 34,							 // Layer-3
+	DST = 35,								 // DST
+};

+ 81 - 0
components/spotify/cspot/bell/include/audio/container/WebmContainer.h

@@ -0,0 +1,81 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-16.
+
+#pragma once
+
+#include "BaseContainer.h"
+
+enum class ElementId;
+
+class WebmContainer : public BaseContainer {
+  public:
+	~WebmContainer();
+	bool parse() override;
+	int32_t getLoadingOffset(uint32_t timeMs) override;
+	bool seekTo(uint32_t timeMs) override;
+	int32_t getCurrentTimeMs() override;
+	uint8_t *readSample(uint32_t &len) override;
+	uint8_t *getSetupData(uint32_t &len, AudioCodec matchCodec) override;
+	void feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) override;
+
+  private:
+	typedef struct {
+		uint32_t time;
+		uint32_t offset;
+	} CuePoint;
+
+  private:
+	// used while parsing
+	uint32_t esize;
+	ElementId eid;
+	// container parameters
+	char *docType = nullptr;
+	uint8_t audioTrackId = 255;
+	float timescale = 0.0f;
+	char *codecId = nullptr;
+	uint8_t *codecPrivate = nullptr;
+	uint32_t codecPrivateLen = 0;
+	// container state
+	CuePoint *cues = nullptr;
+	uint16_t cuesLen = 0;
+	uint32_t clusterEnd = 0;
+	uint32_t clusterTime = 0;
+	uint32_t currentTime = 0;
+	bool isParsed = false;
+	// buffer
+	uint8_t *sampleData = nullptr;
+	uint32_t sampleLen = 0;
+	// lacing parameters
+	uint32_t *laceSizes = nullptr;
+	uint32_t *laceCurrent = nullptr;
+	uint8_t laceLeft = 0;
+	// set to read the current buffer instead of loading new frames
+	uint16_t readOutFrameSize = 0;
+
+  private:
+	uint32_t readVarNum32(bool raw = false);
+	uint64_t readVarNum64();
+	uint32_t readUint(uint8_t len);
+	uint64_t readUlong(uint8_t len);
+	float readFloat(uint8_t len);
+	void readElem();
+	void parseSegment(uint32_t start);
+	void parseTrack(uint32_t end);
+	void parseCuePoint(uint16_t idx, uint32_t end, uint32_t segmentStart);
+	/**
+	 * Continue reading elements until a block is encountered.
+	 *
+	 * If [untilTime] is set, the method will keep reading until [currentTime]
+	 * is less than [untilTime]. Because of how WebM works, [pos] will be one frame later
+	 * than the requested time, although the container will report the correct position.
+	 *
+	 * @return size of the frame pointed by [pos]
+	 */
+	uint32_t readCluster(uint32_t untilTime = 0);
+	/**
+	 * Parse a single block within a cluster. This method will populate lacing parameters if needed.
+	 * @param end offset of the next byte after this block
+	 * @return size of the frame pointed by [pos]
+	 */
+	uint32_t readBlock(uint32_t end);
+	uint8_t *readFrame(uint32_t size, uint32_t &outLen);
+};

+ 713 - 0
components/spotify/cspot/bell/include/audio/container/WebmElements.h

@@ -0,0 +1,713 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-16.
+
+#pragma once
+
+enum class ElementId {
+	/** [sub-elements] Set the EBML characteristics of the data to follow. Each EBML document has to start with this. */
+	EBML = 0x1A45DFA3,
+	/** [u-integer] The version of EBML parser used to create the file. */
+	EBMLVersion = 0x4286,
+	/** [u-integer] The minimum EBML version a parser has to support to read this file. */
+	EBMLReadVersion = 0x42F7,
+	/** [u-integer] The maximum length of the IDs you'll find in this file (4 or less in Matroska). */
+	EBMLMaxIDLength = 0x42F2,
+	/** [u-integer] The maximum length of the sizes you'll find in this file (8 or less in Matroska). This does not
+	   override the element size indicated at the beginning of an element. Elements that have an indicated size which is
+	   larger than what is allowed by EBMLMaxSizeLength shall be considered invalid. */
+	EBMLMaxSizeLength = 0x42F3,
+	/** [string] A string that describes the type of document that follows this EBML header ('matroska' in our case). */
+	DocType = 0x4282,
+	/** [u-integer] The version of DocType interpreter used to create the file. */
+	DocTypeVersion = 0x4287,
+	/** [u-integer] The minimum DocType version an interpreter has to support to read this file. */
+	DocTypeReadVersion = 0x4285,
+	/** [binary] The CRC is computed on all the data from the last CRC element (or start of the upper level element), up
+	   to the CRC element, including other previous CRC elements. All level 1 elements should include a CRC-32. */
+	CRC32 = 0xBF,
+	/** [binary] Used to void damaged data, to avoid unexpected behaviors when using damaged data. The content is
+	   discarded. Also used to reserve space in a sub-element for later use. */
+	Void = 0xEC,
+	/** [sub-elements] Contain signature of some (coming) elements in the stream. */
+	SignatureSlot = 0x1B538667,
+	/** [u-integer] Signature algorithm used (1=RSA, 2=elliptic). */
+	SignatureAlgo = 0x7E8A,
+	/** [u-integer] Hash algorithm used (1=SHA1-160, 2=MD5). */
+	SignatureHash = 0x7E9A,
+	/** [binary] The public key to use with the algorithm (in the case of a PKI-based signature). */
+	SignaturePublicKey = 0x7EA5,
+	/** [binary] The signature of the data (until a new. */
+	Signature = 0x7EB5,
+	/** [sub-elements] Contains elements that will be used to compute the signature. */
+	SignatureElements = 0x7E5B,
+	/** [sub-elements] A list consists of a number of consecutive elements that represent one case where data is used in
+	   signature. Ex: Cluster|Block|BlockAdditional means that the BlockAdditional of all Blocks in all Clusters is used
+	   for encryption. */
+	SignatureElementList = 0x7E7B,
+	/** [binary] An element ID whose data will be used to compute the signature. */
+	SignedElement = 0x6532,
+
+	/* ebml_matroska.xml */
+	/** [master] The Root Element that contains all other Top-Level Elements (Elements defined only at Level 1). A
+	   Matroska file is composed of 1 Segment. */
+	Segment = 0x18538067,
+	/** [master] Contains the Segment Position of other Top-Level Elements. */
+	SeekHead = 0x114D9B74,
+	/** [master] Contains a single seek entry to an EBML Element. */
+	Seek = 0x4DBB,
+	/** [binary] The binary ID corresponding to the Element name. */
+	SeekID = 0x53AB,
+	/** [uinteger] The Segment Position of the Element. */
+	SeekPosition = 0x53AC,
+	/** [master] Contains general information about the Segment. */
+	Info = 0x1549A966,
+	/** [binary] A randomly generated unique ID to identify the Segment amongst many others (128 bits). */
+	SegmentUID = 0x73A4,
+	/** [utf-8] A filename corresponding to this Segment. */
+	SegmentFilename = 0x7384,
+	/** [binary] A unique ID to identify the previous Segment of a Linked Segment (128 bits). */
+	PrevUID = 0x3CB923,
+	/** [utf-8] A filename corresponding to the file of the previous Linked Segment. */
+	PrevFilename = 0x3C83AB,
+	/** [binary] A unique ID to identify the next Segment of a Linked Segment (128 bits). */
+	NextUID = 0x3EB923,
+	/** [utf-8] A filename corresponding to the file of the next Linked Segment. */
+	NextFilename = 0x3E83BB,
+	/** [binary] A randomly generated unique ID that all Segments of a Linked Segment **MUST** share (128 bits). */
+	SegmentFamily = 0x4444,
+	/** [master] The mapping between this `Segment` and a segment value in the given Chapter Codec. */
+	ChapterTranslate = 0x6924,
+	/** [binary] The binary value used to represent this Segment in the chapter codec data. The format depends on the
+	   ChapProcessCodecID used; see (#chapprocesscodecid-element). */
+	ChapterTranslateID = 0x69A5,
+	/** [uinteger] This `ChapterTranslate` applies to this chapter codec of the given chapter edition(s); see
+	   (#chapprocesscodecid-element). */
+	ChapterTranslateCodec = 0x69BF,
+	/** [uinteger] Specify a chapter edition UID on which this `ChapterTranslate` applies. */
+	ChapterTranslateEditionUID = 0x69FC,
+	/** [uinteger] Timestamp scale in nanoseconds (1.000.000 means all timestamps in the Segment are expressed in
+	   milliseconds). */
+	TimestampScale = 0x2AD7B1,
+	/** [float] Duration of the Segment in nanoseconds based on TimestampScale. */
+	Duration = 0x4489,
+	/** [date] The date and time that the Segment was created by the muxing application or library. */
+	DateUTC = 0x4461,
+	/** [utf-8] General name of the Segment. */
+	Title = 0x7BA9,
+	/** [utf-8] Muxing application or library (example: "libmatroska-0.4.3"). */
+	MuxingApp = 0x4D80,
+	/** [utf-8] Writing application (example: "mkvmerge-0.3.3"). */
+	WritingApp = 0x5741,
+	/** [master] The Top-Level Element containing the (monolithic) Block structure. */
+	Cluster = 0x1F43B675,
+	/** [uinteger] Absolute timestamp of the cluster (based on TimestampScale). */
+	Timestamp = 0xE7,
+	/** [master] The list of tracks that are not used in that part of the stream. It is useful when using overlay tracks
+	   on seeking or to decide what track to use. */
+	SilentTracks = 0x5854,
+	/** [uinteger] One of the track number that are not used from now on in the stream. It could change later if not
+	   specified as silent in a further Cluster. */
+	SilentTrackNumber = 0x58D7,
+	/** [uinteger] The Segment Position of the Cluster in the Segment (0 in live streams). It might help to
+	   resynchronise offset on damaged streams. */
+	Position = 0xA7,
+	/** [uinteger] Size of the previous Cluster, in octets. Can be useful for backward playing. */
+	PrevSize = 0xAB,
+	/** [binary] Similar to Block, see (#block-structure), but without all the extra information, mostly used to reduced
+	   overhead when no extra feature is needed; see (#simpleblock-structure) on SimpleBlock Structure. */
+	SimpleBlock = 0xA3,
+	/** [master] Basic container of information containing a single Block and information specific to that Block. */
+	BlockGroup = 0xA0,
+	/** [binary] Block containing the actual data to be rendered and a timestamp relative to the Cluster Timestamp; see
+	   (#block-structure) on Block Structure. */
+	Block = 0xA1,
+	/** [binary] A Block with no data. It **MUST** be stored in the stream at the place the real Block would be in
+	   display order. */
+	BlockVirtual = 0xA2,
+	/** [master] Contain additional blocks to complete the main one. An EBML parser that has no knowledge of the Block
+	   structure could still see and use/skip these data. */
+	BlockAdditions = 0x75A1,
+	/** [master] Contain the BlockAdditional and some parameters. */
+	BlockMore = 0xA6,
+	/** [uinteger] An ID to identify the BlockAdditional level. If BlockAddIDType of the corresponding block is 0, this
+	   value is also the value of BlockAddIDType for the meaning of the content of BlockAdditional. */
+	BlockAddID = 0xEE,
+	/** [binary] Interpreted by the codec as it wishes (using the BlockAddID). */
+	BlockAdditional = 0xA5,
+	/** [uinteger] The duration of the Block (based on TimestampScale). The BlockDuration Element can be useful at the
+	   end of a Track to define the duration of the last frame (as there is no subsequent Block available), or when
+	   there is a break in a track like for subtitle tracks. */
+	BlockDuration = 0x9B,
+	/** [uinteger] This frame is referenced and has the specified cache priority. In cache only a frame of the same or
+	   higher priority can replace this frame. A value of 0 means the frame is not referenced. */
+	ReferencePriority = 0xFA,
+	/** [integer] A timestamp value, relative to the timestamp of the Block in this BlockGroup. This is used to
+	   reference other frames necessary to decode this frame. The relative value **SHOULD** correspond to a valid
+	   `Block` this `Block` depends on. Historically Matroska Writer didn't write the actual `Block(s)` this `Block`
+	   depends on, but *some* `Block` in the past.  The value "0" **MAY** also be used to signify this `Block` cannot be
+	   decoded on its own, but without knownledge of which `Block` is necessary. In this case, other `ReferenceBlock`
+	   **MUST NOT** be found in the same `BlockGroup`.  If the `BlockGroup` doesn't have any `ReferenceBlock` element,
+	   then the `Block` it contains can be decoded without using any other `Block` data. */
+	ReferenceBlock = 0xFB,
+	/** [integer] The Segment Position of the data that would otherwise be in position of the virtual block. */
+	ReferenceVirtual = 0xFD,
+	/** [binary] The new codec state to use. Data interpretation is private to the codec. This information **SHOULD**
+	   always be referenced by a seek entry. */
+	CodecState = 0xA4,
+	/** [integer] Duration in nanoseconds of the silent data added to the Block (padding at the end of the Block for
+	   positive value, at the beginning of the Block for negative value). The duration of DiscardPadding is not
+	   calculated in the duration of the TrackEntry and **SHOULD** be discarded during playback. */
+	DiscardPadding = 0x75A2,
+	/** [master] Contains slices description. */
+	Slices = 0x8E,
+	/** [master] Contains extra time information about the data contained in the Block. Being able to interpret this
+	   Element is not **REQUIRED** for playback. */
+	TimeSlice = 0xE8,
+	/** [uinteger] The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). Being
+	   able to interpret this Element is not **REQUIRED** for playback. */
+	LaceNumber = 0xCC,
+	/** [uinteger] The number of the frame to generate from this lace with this delay (allow you to generate many frames
+	   from the same Block/Frame). */
+	FrameNumber = 0xCD,
+	/** [uinteger] The ID of the BlockAdditional Element (0 is the main Block). */
+	BlockAdditionID = 0xCB,
+	/** [uinteger] The (scaled) delay to apply to the Element. */
+	Delay = 0xCE,
+	/** [uinteger] The (scaled) duration to apply to the Element. */
+	SliceDuration = 0xCF,
+	/** [master] Contains information about the last reference frame. See [@?DivXTrickTrack]. */
+	ReferenceFrame = 0xC8,
+	/** [uinteger] The relative offset, in bytes, from the previous BlockGroup element for this Smooth FF/RW video track
+	   to the containing BlockGroup element. See [@?DivXTrickTrack]. */
+	ReferenceOffset = 0xC9,
+	/** [uinteger] The timecode of the BlockGroup pointed to by ReferenceOffset. See [@?DivXTrickTrack]. */
+	ReferenceTimestamp = 0xCA,
+	/** [binary] Similar to SimpleBlock, see (#simpleblock-structure), but the data inside the Block are Transformed
+	   (encrypt and/or signed). */
+	EncryptedBlock = 0xAF,
+	/** [master] A Top-Level Element of information with many tracks described. */
+	Tracks = 0x1654AE6B,
+	/** [master] Describes a track with all Elements. */
+	TrackEntry = 0xAE,
+	/** [uinteger] The track number as used in the Block Header (using more than 127 tracks is not encouraged, though
+	   the design allows an unlimited number). */
+	TrackNumber = 0xD7,
+	/** [uinteger] A unique ID to identify the Track. */
+	TrackUID = 0x73C5,
+	/** [uinteger] The `TrackType` defines the type of each frame found in the Track. The value **SHOULD** be stored on
+	   1 octet. */
+	TrackType = 0x83,
+	/** [uinteger] Set to 1 if the track is usable. It is possible to turn a not usable track into a usable track using
+	   chapter codecs or control tracks. */
+	FlagEnabled = 0xB9,
+	/** [uinteger] Set if that track (audio, video or subs) **SHOULD** be eligible for automatic selection by the
+	   player; see (#default-track-selection) for more details. */
+	FlagDefault = 0x88,
+	/** [uinteger] Applies only to subtitles. Set if that track **SHOULD** be eligible for automatic selection by the
+	   player if it matches the user's language preference, even if the user's preferences would normally not enable
+	   subtitles with the selected audio track; this can be used for tracks containing only translations of
+	   foreign-language audio or onscreen text. See (#default-track-selection) for more details. */
+	FlagForced = 0x55AA,
+	/** [uinteger] Set to 1 if that track is suitable for users with hearing impairments, set to 0 if it is unsuitable
+	   for users with hearing impairments. */
+	FlagHearingImpaired = 0x55AB,
+	/** [uinteger] Set to 1 if that track is suitable for users with visual impairments, set to 0 if it is unsuitable
+	   for users with visual impairments. */
+	FlagVisualImpaired = 0x55AC,
+	/** [uinteger] Set to 1 if that track contains textual descriptions of video content, set to 0 if that track does
+	   not contain textual descriptions of video content. */
+	FlagTextDescriptions = 0x55AD,
+	/** [uinteger] Set to 1 if that track is in the content's original language, set to 0 if it is a translation. */
+	FlagOriginal = 0x55AE,
+	/** [uinteger] Set to 1 if that track contains commentary, set to 0 if it does not contain commentary. */
+	FlagCommentary = 0x55AF,
+	/** [uinteger] Set to 1 if the track **MAY** contain blocks using lacing. When set to 0 all blocks **MUST** have
+	   their lacing flags set to No lacing; see (#block-lacing) on Block Lacing. */
+	FlagLacing = 0x9C,
+	/** [uinteger] The minimum number of frames a player **SHOULD** be able to cache during playback. If set to 0, the
+	   reference pseudo-cache system is not used. */
+	MinCache = 0x6DE7,
+	/** [uinteger] The maximum cache size necessary to store referenced frames in and the current frame. 0 means no
+	   cache is needed. */
+	MaxCache = 0x6DF8,
+	/** [uinteger] Number of nanoseconds (not scaled via TimestampScale) per frame (frame in the Matroska sense -- one
+	   Element put into a (Simple)Block). */
+	DefaultDuration = 0x23E383,
+	/** [uinteger] The period in nanoseconds (not scaled by TimestampScale) between two successive fields at the output
+	   of the decoding process, see (#defaultdecodedfieldduration) for more information */
+	DefaultDecodedFieldDuration = 0x234E7A,
+	/** [float] DEPRECATED, DO NOT USE. The scale to apply on this track to work at normal speed in relation with other
+	   tracks (mostly used to adjust video speed when the audio length differs). */
+	TrackTimestampScale = 0x23314F,
+	/** [integer] A value to add to the Block's Timestamp. This can be used to adjust the playback offset of a track. */
+	TrackOffset = 0x537F,
+	/** [uinteger] The maximum value of BlockAddID ((#blockaddid-element)). A value 0 means there is no BlockAdditions
+	   ((#blockadditions-element)) for this track. */
+	MaxBlockAdditionID = 0x55EE,
+	/** [master] Contains elements that extend the track format, by adding content either to each frame, with BlockAddID
+	   ((#blockaddid-element)), or to the track as a whole with BlockAddIDExtraData. */
+	BlockAdditionMapping = 0x41E4,
+	/** [uinteger] If the track format extension needs content beside frames, the value refers to the BlockAddID
+	   ((#blockaddid-element)), value being described. To keep MaxBlockAdditionID as low as possible, small values
+	   **SHOULD** be used. */
+	BlockAddIDValue = 0x41F0,
+	/** [string] A human-friendly name describing the type of BlockAdditional data, as defined by the associated Block
+	   Additional Mapping. */
+	BlockAddIDName = 0x41A4,
+	/** [uinteger] Stores the registered identifier of the Block Additional Mapping to define how the BlockAdditional
+	   data should be handled. */
+	BlockAddIDType = 0x41E7,
+	/** [binary] Extra binary data that the BlockAddIDType can use to interpret the BlockAdditional data. The
+	   interpretation of the binary data depends on the BlockAddIDType value and the corresponding Block Additional
+	   Mapping. */
+	BlockAddIDExtraData = 0x41ED,
+	/** [utf-8] A human-readable track name. */
+	Name = 0x536E,
+	/** [string] Specifies the language of the track in the Matroska languages form; see (#language-codes) on language
+	   codes. This Element **MUST** be ignored if the LanguageIETF Element is used in the same TrackEntry. */
+	Language = 0x22B59C,
+	/** [string] Specifies the language of the track according to [@!BCP47] and using the IANA Language Subtag Registry
+	   [@!IANALangRegistry]. If this Element is used, then any Language Elements used in the same TrackEntry **MUST** be
+	   ignored. */
+	LanguageIETF = 0x22B59D,
+	/** [string] An ID corresponding to the codec, see [@!MatroskaCodec] for more info. */
+	CodecID = 0x86,
+	/** [binary] Private data only known to the codec. */
+	CodecPrivate = 0x63A2,
+	/** [utf-8] A human-readable string specifying the codec. */
+	CodecName = 0x258688,
+	/** [uinteger] The UID of an attachment that is used by this codec. */
+	AttachmentLink = 0x7446,
+	/** [utf-8] A string describing the encoding setting used. */
+	CodecSettings = 0x3A9697,
+	/** [string] A URL to find information about the codec used. */
+	CodecInfoURL = 0x3B4040,
+	/** [string] A URL to download about the codec used. */
+	CodecDownloadURL = 0x26B240,
+	/** [uinteger] Set to 1 if the codec can decode potentially damaged data. */
+	CodecDecodeAll = 0xAA,
+	/** [uinteger] Specify that this track is an overlay track for the Track specified (in the u-integer). That means
+	   when this track has a gap, see (#silenttracks-element) on SilentTracks, the overlay track **SHOULD** be used
+	   instead. The order of multiple TrackOverlay matters, the first one is the one that **SHOULD** be used. If not
+	   found it **SHOULD** be the second, etc. */
+	TrackOverlay = 0x6FAB,
+	/** [uinteger] CodecDelay is The codec-built-in delay in nanoseconds. This value **MUST** be subtracted from each
+	   block timestamp in order to get the actual timestamp. The value **SHOULD** be small so the muxing of tracks with
+	   the same actual timestamp are in the same Cluster. */
+	CodecDelay = 0x56AA,
+	/** [uinteger] After a discontinuity, SeekPreRoll is the duration in nanoseconds of the data the decoder **MUST**
+	   decode before the decoded data is valid. */
+	SeekPreRoll = 0x56BB,
+	/** [master] The mapping between this `TrackEntry` and a track value in the given Chapter Codec. */
+	TrackTranslate = 0x6624,
+	/** [binary] The binary value used to represent this `TrackEntry` in the chapter codec data. The format depends on
+	   the `ChapProcessCodecID` used; see (#chapprocesscodecid-element). */
+	TrackTranslateTrackID = 0x66A5,
+	/** [uinteger] This `TrackTranslate` applies to this chapter codec of the given chapter edition(s); see
+	   (#chapprocesscodecid-element). */
+	TrackTranslateCodec = 0x66BF,
+	/** [uinteger] Specify a chapter edition UID on which this `TrackTranslate` applies. */
+	TrackTranslateEditionUID = 0x66FC,
+	/** [master] Video settings. */
+	Video = 0xE0,
+	/** [uinteger] Specify whether the video frames in this track are interlaced or not. */
+	FlagInterlaced = 0x9A,
+	/** [uinteger] Specify the field ordering of video frames in this track. */
+	FieldOrder = 0x9D,
+	/** [uinteger] Stereo-3D video mode. There are some more details in (#multi-planar-and-3d-videos). */
+	StereoMode = 0x53B8,
+	/** [uinteger] Alpha Video Mode. Presence of this Element indicates that the BlockAdditional Element could contain
+	   Alpha data. */
+	AlphaMode = 0x53C0,
+	/** [uinteger] DEPRECATED, DO NOT USE. Bogus StereoMode value used in old versions of libmatroska. */
+	OldStereoMode = 0x53B9,
+	/** [uinteger] Width of the encoded video frames in pixels. */
+	PixelWidth = 0xB0,
+	/** [uinteger] Height of the encoded video frames in pixels. */
+	PixelHeight = 0xBA,
+	/** [uinteger] The number of video pixels to remove at the bottom of the image. */
+	PixelCropBottom = 0x54AA,
+	/** [uinteger] The number of video pixels to remove at the top of the image. */
+	PixelCropTop = 0x54BB,
+	/** [uinteger] The number of video pixels to remove on the left of the image. */
+	PixelCropLeft = 0x54CC,
+	/** [uinteger] The number of video pixels to remove on the right of the image. */
+	PixelCropRight = 0x54DD,
+	/** [uinteger] Width of the video frames to display. Applies to the video frame after cropping (PixelCrop*
+	   Elements). */
+	DisplayWidth = 0x54B0,
+	/** [uinteger] Height of the video frames to display. Applies to the video frame after cropping (PixelCrop*
+	   Elements). */
+	DisplayHeight = 0x54BA,
+	/** [uinteger] How DisplayWidth & DisplayHeight are interpreted. */
+	DisplayUnit = 0x54B2,
+	/** [uinteger] Specify the possible modifications to the aspect ratio. */
+	AspectRatioType = 0x54B3,
+	/** [binary] Specify the uncompressed pixel format used for the Track's data as a FourCC. This value is similar in
+	   scope to the biCompression value of AVI's `BITMAPINFO` [@?AVIFormat]. See the YUV video formats [@?FourCC-YUV]
+	   and RGB video formats [@?FourCC-RGB] for common values. */
+	UncompressedFourCC = 0x2EB524,
+	/** [float] Gamma Value. */
+	GammaValue = 0x2FB523,
+	/** [float] Number of frames per second. This value is Informational only. It is intended for constant frame rate
+	   streams, and **SHOULD NOT** be used for a variable frame rate TrackEntry. */
+	FrameRate = 0x2383E3,
+	/** [master] Settings describing the colour format. */
+	Colour = 0x55B0,
+	/** [uinteger] The Matrix Coefficients of the video used to derive luma and chroma values from red, green, and blue
+	   color primaries. For clarity, the value and meanings for MatrixCoefficients are adopted from Table 4 of ISO/IEC
+	   23001-8:2016 or ITU-T H.273. */
+	MatrixCoefficients = 0x55B1,
+	/** [uinteger] Number of decoded bits per channel. A value of 0 indicates that the BitsPerChannel is unspecified. */
+	BitsPerChannel = 0x55B2,
+	/** [uinteger] The amount of pixels to remove in the Cr and Cb channels for every pixel not removed horizontally.
+	   Example: For video with 4:2:0 chroma subsampling, the ChromaSubsamplingHorz **SHOULD** be set to 1. */
+	ChromaSubsamplingHorz = 0x55B3,
+	/** [uinteger] The amount of pixels to remove in the Cr and Cb channels for every pixel not removed vertically.
+	   Example: For video with 4:2:0 chroma subsampling, the ChromaSubsamplingVert **SHOULD** be set to 1. */
+	ChromaSubsamplingVert = 0x55B4,
+	/** [uinteger] The amount of pixels to remove in the Cb channel for every pixel not removed horizontally. This is
+	   additive with ChromaSubsamplingHorz. Example: For video with 4:2:1 chroma subsampling, the ChromaSubsamplingHorz
+	   **SHOULD** be set to 1 and CbSubsamplingHorz **SHOULD** be set to 1. */
+	CbSubsamplingHorz = 0x55B5,
+	/** [uinteger] The amount of pixels to remove in the Cb channel for every pixel not removed vertically. This is
+	   additive with ChromaSubsamplingVert. */
+	CbSubsamplingVert = 0x55B6,
+	/** [uinteger] How chroma is subsampled horizontally. */
+	ChromaSitingHorz = 0x55B7,
+	/** [uinteger] How chroma is subsampled vertically. */
+	ChromaSitingVert = 0x55B8,
+	/** [uinteger] Clipping of the color ranges. */
+	Range = 0x55B9,
+	/** [uinteger] The transfer characteristics of the video. For clarity, the value and meanings for
+	   TransferCharacteristics are adopted from Table 3 of ISO/IEC 23091-4 or ITU-T H.273. */
+	TransferCharacteristics = 0x55BA,
+	/** [uinteger] The colour primaries of the video. For clarity, the value and meanings for Primaries are adopted from
+	   Table 2 of ISO/IEC 23091-4 or ITU-T H.273. */
+	Primaries = 0x55BB,
+	/** [uinteger] Maximum brightness of a single pixel (Maximum Content Light Level) in candelas per square meter
+	   (cd/m^2^). */
+	MaxCLL = 0x55BC,
+	/** [uinteger] Maximum brightness of a single full frame (Maximum Frame-Average Light Level) in candelas per square
+	   meter (cd/m^2^). */
+	MaxFALL = 0x55BD,
+	/** [master] SMPTE 2086 mastering data. */
+	MasteringMetadata = 0x55D0,
+	/** [float] Red X chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryRChromaticityX = 0x55D1,
+	/** [float] Red Y chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryRChromaticityY = 0x55D2,
+	/** [float] Green X chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryGChromaticityX = 0x55D3,
+	/** [float] Green Y chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryGChromaticityY = 0x55D4,
+	/** [float] Blue X chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryBChromaticityX = 0x55D5,
+	/** [float] Blue Y chromaticity coordinate, as defined by CIE 1931. */
+	PrimaryBChromaticityY = 0x55D6,
+	/** [float] White X chromaticity coordinate, as defined by CIE 1931. */
+	WhitePointChromaticityX = 0x55D7,
+	/** [float] White Y chromaticity coordinate, as defined by CIE 1931. */
+	WhitePointChromaticityY = 0x55D8,
+	/** [float] Maximum luminance. Represented in candelas per square meter (cd/m^2^). */
+	LuminanceMax = 0x55D9,
+	/** [float] Minimum luminance. Represented in candelas per square meter (cd/m^2^). */
+	LuminanceMin = 0x55DA,
+	/** [master] Describes the video projection details. Used to render spherical, VR videos or flipping videos
+	   horizontally/vertically. */
+	Projection = 0x7670,
+	/** [uinteger] Describes the projection used for this video track. */
+	ProjectionType = 0x7671,
+	/** [binary] Private data that only applies to a specific projection.  *  If `ProjectionType` equals 0
+	   (Rectangular),      then this element must not be present. *  If `ProjectionType` equals 1 (Equirectangular),
+	   then this element must be present and contain the same binary data that would be stored inside       an ISOBMFF
+	   Equirectangular Projection Box ('equi'). *  If `ProjectionType` equals 2 (Cubemap), then this element must be
+	   present and contain the same binary data that would be stored       inside an ISOBMFF Cubemap Projection Box
+	   ('cbmp'). *  If `ProjectionType` equals 3 (Mesh), then this element must be present and contain the same binary
+	   data that would be stored inside        an ISOBMFF Mesh Projection Box ('mshp'). */
+	ProjectionPrivate = 0x7672,
+	/** [float] Specifies a yaw rotation to the projection.  Value represents a clockwise rotation, in degrees, around
+	   the up vector. This rotation must be applied before any `ProjectionPosePitch` or `ProjectionPoseRoll` rotations.
+	   The value of this element **MUST** be in the -180 to 180 degree range, both included.  Setting
+	   `ProjectionPoseYaw` to 180 or -180 degrees, with the `ProjectionPoseRoll` and `ProjectionPosePitch` set to 0
+	   degrees flips the image horizontally. */
+	ProjectionPoseYaw = 0x7673,
+	/** [float] Specifies a pitch rotation to the projection.  Value represents a counter-clockwise rotation, in
+	   degrees, around the right vector. This rotation must be applied after the `ProjectionPoseYaw` rotation and before
+	   the `ProjectionPoseRoll` rotation. The value of this element **MUST** be in the -90 to 90 degree range, both
+	   included. */
+	ProjectionPosePitch = 0x7674,
+	/** [float] Specifies a roll rotation to the projection.  Value represents a counter-clockwise rotation, in degrees,
+	   around the forward vector. This rotation must be applied after the `ProjectionPoseYaw` and `ProjectionPosePitch`
+	   rotations. The value of this element **MUST** be in the -180 to 180 degree range, both included.  Setting
+	   `ProjectionPoseRoll` to 180 or -180 degrees, the `ProjectionPoseYaw` to 180 or -180 degrees with
+	   `ProjectionPosePitch` set to 0 degrees flips the image vertically.  Setting `ProjectionPoseRoll` to 180 or -180
+	   degrees, with the `ProjectionPoseYaw` and `ProjectionPosePitch` set to 0 degrees flips the image horizontally and
+	   vertically. */
+	ProjectionPoseRoll = 0x7675,
+	/** [master] Audio settings. */
+	Audio = 0xE1,
+	/** [float] Sampling frequency in Hz. */
+	SamplingFrequency = 0xB5,
+	/** [float] Real output sampling frequency in Hz (used for SBR techniques). */
+	OutputSamplingFrequency = 0x78B5,
+	/** [uinteger] Numbers of channels in the track. */
+	Channels = 0x9F,
+	/** [binary] Table of horizontal angles for each successive channel. */
+	ChannelPositions = 0x7D7B,
+	/** [uinteger] Bits per sample, mostly used for PCM. */
+	BitDepth = 0x6264,
+	/** [master] Operation that needs to be applied on tracks to create this virtual track. For more details look at
+	   (#track-operation). */
+	TrackOperation = 0xE2,
+	/** [master] Contains the list of all video plane tracks that need to be combined to create this 3D track */
+	TrackCombinePlanes = 0xE3,
+	/** [master] Contains a video plane track that need to be combined to create this 3D track */
+	TrackPlane = 0xE4,
+	/** [uinteger] The trackUID number of the track representing the plane. */
+	TrackPlaneUID = 0xE5,
+	/** [uinteger] The kind of plane this track corresponds to. */
+	TrackPlaneType = 0xE6,
+	/** [master] Contains the list of all tracks whose Blocks need to be combined to create this virtual track */
+	TrackJoinBlocks = 0xE9,
+	/** [uinteger] The trackUID number of a track whose blocks are used to create this virtual track. */
+	TrackJoinUID = 0xED,
+	/** [uinteger] The TrackUID of the Smooth FF/RW video in the paired EBML structure corresponding to this video
+	   track. See [@?DivXTrickTrack]. */
+	TrickTrackUID = 0xC0,
+	/** [binary] The SegmentUID of the Segment containing the track identified by TrickTrackUID. See [@?DivXTrickTrack].
+	 */
+	TrickTrackSegmentUID = 0xC1,
+	/** [uinteger] Set to 1 if this video track is a Smooth FF/RW track. If set to 1, MasterTrackUID and
+	   MasterTrackSegUID should must be present and BlockGroups for this track must contain ReferenceFrame structures.
+	   Otherwise, TrickTrackUID and TrickTrackSegUID must be present if this track has a corresponding Smooth FF/RW
+	   track. See [@?DivXTrickTrack]. */
+	TrickTrackFlag = 0xC6,
+	/** [uinteger] The TrackUID of the video track in the paired EBML structure that corresponds to this Smooth FF/RW
+	   track. See [@?DivXTrickTrack]. */
+	TrickMasterTrackUID = 0xC7,
+	/** [binary] The SegmentUID of the Segment containing the track identified by MasterTrackUID. See
+	   [@?DivXTrickTrack]. */
+	TrickMasterTrackSegmentUID = 0xC4,
+	/** [master] Settings for several content encoding mechanisms like compression or encryption. */
+	ContentEncodings = 0x6D80,
+	/** [master] Settings for one content encoding like compression or encryption. */
+	ContentEncoding = 0x6240,
+	/** [uinteger] Tells when this modification was used during encoding/muxing starting with 0 and counting upwards.
+	   The decoder/demuxer has to start with the highest order number it finds and work its way down. This value has to
+	   be unique over all ContentEncodingOrder Elements in the TrackEntry that contains this ContentEncodingOrder
+	   element. */
+	ContentEncodingOrder = 0x5031,
+	/** [uinteger] A bit field that describes which Elements have been modified in this way. Values (big-endian) can be
+	   OR'ed. */
+	ContentEncodingScope = 0x5032,
+	/** [uinteger] A value describing what kind of transformation is applied. */
+	ContentEncodingType = 0x5033,
+	/** [master] Settings describing the compression used. This Element **MUST** be present if the value of
+	   ContentEncodingType is 0 and absent otherwise. Each block **MUST** be decompressable even if no previous block is
+	   available in order not to prevent seeking. */
+	ContentCompression = 0x5034,
+	/** [uinteger] The compression algorithm used. */
+	ContentCompAlgo = 0x4254,
+	/** [binary] Settings that might be needed by the decompressor. For Header Stripping (`ContentCompAlgo`=3), the
+	   bytes that were removed from the beginning of each frames of the track. */
+	ContentCompSettings = 0x4255,
+	/** [master] Settings describing the encryption used. This Element **MUST** be present if the value of
+	   `ContentEncodingType` is 1 (encryption) and **MUST** be ignored otherwise. */
+	ContentEncryption = 0x5035,
+	/** [uinteger] The encryption algorithm used. The value "0" means that the contents have not been encrypted. */
+	ContentEncAlgo = 0x47E1,
+	/** [binary] For public key algorithms this is the ID of the public key the the data was encrypted with. */
+	ContentEncKeyID = 0x47E2,
+	/** [master] Settings describing the encryption algorithm used. If `ContentEncAlgo` != 5 this **MUST** be ignored.
+	 */
+	ContentEncAESSettings = 0x47E7,
+	/** [uinteger] The AES cipher mode used in the encryption. */
+	AESSettingsCipherMode = 0x47E8,
+	/** [binary] A cryptographic signature of the contents. */
+	ContentSignature = 0x47E3,
+	/** [binary] This is the ID of the private key the data was signed with. */
+	ContentSigKeyID = 0x47E4,
+	/** [uinteger] The algorithm used for the signature. */
+	ContentSigAlgo = 0x47E5,
+	/** [uinteger] The hash algorithm used for the signature. */
+	ContentSigHashAlgo = 0x47E6,
+	/** [master] A Top-Level Element to speed seeking access. All entries are local to the Segment. */
+	Cues = 0x1C53BB6B,
+	/** [master] Contains all information relative to a seek point in the Segment. */
+	CuePoint = 0xBB,
+	/** [uinteger] Absolute timestamp according to the Segment time base. */
+	CueTime = 0xB3,
+	/** [master] Contain positions for different tracks corresponding to the timestamp. */
+	CueTrackPositions = 0xB7,
+	/** [uinteger] The track for which a position is given. */
+	CueTrack = 0xF7,
+	/** [uinteger] The Segment Position of the Cluster containing the associated Block. */
+	CueClusterPosition = 0xF1,
+	/** [uinteger] The relative position inside the Cluster of the referenced SimpleBlock or BlockGroup with 0 being the
+	   first possible position for an Element inside that Cluster. */
+	CueRelativePosition = 0xF0,
+	/** [uinteger] The duration of the block according to the Segment time base. If missing the track's DefaultDuration
+	   does not apply and no duration information is available in terms of the cues. */
+	CueDuration = 0xB2,
+	/** [uinteger] Number of the Block in the specified Cluster. */
+	CueBlockNumber = 0x5378,
+	/** [uinteger] The Segment Position of the Codec State corresponding to this Cue Element. 0 means that the data is
+	   taken from the initial Track Entry. */
+	CueCodecState = 0xEA,
+	/** [master] The Clusters containing the referenced Blocks. */
+	CueReference = 0xDB,
+	/** [uinteger] Timestamp of the referenced Block. */
+	CueRefTime = 0x96,
+	/** [uinteger] The Segment Position of the Cluster containing the referenced Block. */
+	CueRefCluster = 0x97,
+	/** [uinteger] Number of the referenced Block of Track X in the specified Cluster. */
+	CueRefNumber = 0x535F,
+	/** [uinteger] The Segment Position of the Codec State corresponding to this referenced Element. 0 means that the
+	   data is taken from the initial Track Entry. */
+	CueRefCodecState = 0xEB,
+	/** [master] Contain attached files. */
+	Attachments = 0x1941A469,
+	/** [master] An attached file. */
+	AttachedFile = 0x61A7,
+	/** [utf-8] A human-friendly name for the attached file. */
+	FileDescription = 0x467E,
+	/** [utf-8] Filename of the attached file. */
+	FileName = 0x466E,
+	/** [string] MIME type of the file. */
+	FileMimeType = 0x4660,
+	/** [binary] The data of the file. */
+	FileData = 0x465C,
+	/** [uinteger] Unique ID representing the file, as random as possible. */
+	FileUID = 0x46AE,
+	/** [binary] A binary value that a track/codec can refer to when the attachment is needed. */
+	FileReferral = 0x4675,
+	/** [uinteger] The timecode at which this optimized font attachment comes into context, based on the Segment
+	   TimecodeScale. This element is reserved for future use and if written must be the segment start time. See
+	   [@?DivXWorldFonts]. */
+	FileUsedStartTime = 0x4661,
+	/** [uinteger] The timecode at which this optimized font attachment goes out of context, based on the Segment
+	   TimecodeScale. This element is reserved for future use and if written must be the segment end time. See
+	   [@?DivXWorldFonts]. */
+	FileUsedEndTime = 0x4662,
+	/** [master] A system to define basic menus and partition data. For more detailed information, look at the Chapters
+	   explanation in (#chapters). */
+	Chapters = 0x1043A770,
+	/** [master] Contains all information about a Segment edition. */
+	EditionEntry = 0x45B9,
+	/** [uinteger] A unique ID to identify the edition. It's useful for tagging an edition. */
+	EditionUID = 0x45BC,
+	/** [uinteger] Set to 1 if an edition is hidden. Hidden editions **SHOULD NOT** be available to the user interface
+	   (but still to Control Tracks; see (#chapter-flags) on Chapter flags). */
+	EditionFlagHidden = 0x45BD,
+	/** [uinteger] Set to 1 if the edition **SHOULD** be used as the default one. */
+	EditionFlagDefault = 0x45DB,
+	/** [uinteger] Set to 1 if the chapters can be defined multiple times and the order to play them is enforced; see
+	   (#editionflagordered). */
+	EditionFlagOrdered = 0x45DD,
+	/** [master] Contains the atom information to use as the chapter atom (apply to all tracks). */
+	ChapterAtom = 0xB6,
+	/** [uinteger] A unique ID to identify the Chapter. */
+	ChapterUID = 0x73C4,
+	/** [utf-8] A unique string ID to identify the Chapter. Use for WebVTT cue identifier storage [@!WebVTT]. */
+	ChapterStringUID = 0x5654,
+	/** [uinteger] Timestamp of the start of Chapter (not scaled). */
+	ChapterTimeStart = 0x91,
+	/** [uinteger] Timestamp of the end of Chapter (timestamp excluded, not scaled). The value **MUST** be greater than
+	   or equal to the `ChapterTimeStart` of the same `ChapterAtom`. */
+	ChapterTimeEnd = 0x92,
+	/** [uinteger] Set to 1 if a chapter is hidden. Hidden chapters **SHOULD NOT** be available to the user interface
+	   (but still to Control Tracks; see (#chapterflaghidden) on Chapter flags). */
+	ChapterFlagHidden = 0x98,
+	/** [uinteger] Set to 1 if the chapter is enabled. It can be enabled/disabled by a Control Track. When disabled, the
+	   movie **SHOULD** skip all the content between the TimeStart and TimeEnd of this chapter; see (#chapter-flags) on
+	   Chapter flags. */
+	ChapterFlagEnabled = 0x4598,
+	/** [binary] The SegmentUID of another Segment to play during this chapter. */
+	ChapterSegmentUID = 0x6E67,
+	/** [uinteger] The EditionUID to play from the Segment linked in ChapterSegmentUID. If ChapterSegmentEditionUID is
+	   undeclared, then no Edition of the linked Segment is used; see (#medium-linking) on medium-linking Segments. */
+	ChapterSegmentEditionUID = 0x6EBC,
+	/** [uinteger] Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50); see
+	   (#physical-types) for a complete list of values. */
+	ChapterPhysicalEquiv = 0x63C3,
+	/** [master] List of tracks on which the chapter applies. If this Element is not present, all tracks apply */
+	ChapterTrack = 0x8F,
+	/** [uinteger] UID of the Track to apply this chapter to. In the absence of a control track, choosing this chapter
+	   will select the listed Tracks and deselect unlisted tracks. Absence of this Element indicates that the Chapter
+	   **SHOULD** be applied to any currently used Tracks. */
+	ChapterTrackUID = 0x89,
+	/** [master] Contains all possible strings to use for the chapter display. */
+	ChapterDisplay = 0x80,
+	/** [utf-8] Contains the string to use as the chapter atom. */
+	ChapString = 0x85,
+	/** [string] A language corresponding to the string, in the bibliographic ISO-639-2 form [@!ISO639-2]. This Element
+	 **MUST** be ignored if a ChapLanguageIETF Element is used within the same ChapterDisplay Element. */
+	ChapLanguage = 0x437C,
+	/** [string] Specifies a language corresponding to the ChapString in the format defined in [@!BCP47] and using the
+	   IANA Language Subtag Registry [@!IANALangRegistry]. If a ChapLanguageIETF Element is used, then any ChapLanguage
+	   and ChapCountry Elements used in the same ChapterDisplay **MUST** be ignored. */
+	ChapLanguageIETF = 0x437D,
+	/** [string] A country corresponding to the string, using the same 2 octets country-codes as in Internet domains
+	   [@!IANADomains] based on [@!ISO3166-1] alpha-2 codes. This Element **MUST** be ignored if a ChapLanguageIETF
+	   Element is used within the same ChapterDisplay Element. */
+	ChapCountry = 0x437E,
+	/** [master] Contains all the commands associated to the Atom. */
+	ChapProcess = 0x6944,
+	/** [uinteger] Contains the type of the codec used for the processing. A value of 0 means native Matroska processing
+	   (to be defined), a value of 1 means the DVD command set is used; see (#menu-features) on DVD menus. More codec
+	   IDs can be added later. */
+	ChapProcessCodecID = 0x6955,
+	/** [binary] Some optional data attached to the ChapProcessCodecID information.     For ChapProcessCodecID = 1, it
+	   is the "DVD level" equivalent; see (#menu-features) on DVD menus. */
+	ChapProcessPrivate = 0x450D,
+	/** [master] Contains all the commands associated to the Atom. */
+	ChapProcessCommand = 0x6911,
+	/** [uinteger] Defines when the process command **SHOULD** be handled */
+	ChapProcessTime = 0x6922,
+	/** [binary] Contains the command information. The data **SHOULD** be interpreted depending on the
+	   ChapProcessCodecID value. For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post
+	   commands; see (#menu-features) on DVD menus. */
+	ChapProcessData = 0x6933,
+	/** [master] Element containing metadata describing Tracks, Editions, Chapters, Attachments, or the Segment as a
+	   whole. A list of valid tags can be found in [@!MatroskaTags]. */
+	Tags = 0x1254C367,
+	/** [master] A single metadata descriptor. */
+	Tag = 0x7373,
+	/** [master] Specifies which other elements the metadata represented by the Tag applies to. If empty or not present,
+	   then the Tag describes everything in the Segment. */
+	Targets = 0x63C0,
+	/** [uinteger] A number to indicate the logical level of the target. */
+	TargetTypeValue = 0x68CA,
+	/** [string] An informational string that can be used to display the logical level of the target like "ALBUM",
+	   "TRACK", "MOVIE", "CHAPTER", etc ; see Section 6.4 of [@!MatroskaTags]. */
+	TargetType = 0x63CA,
+	/** [uinteger] A unique ID to identify the Track(s) the tags belong to. */
+	TagTrackUID = 0x63C5,
+	/** [uinteger] A unique ID to identify the EditionEntry(s) the tags belong to. */
+	TagEditionUID = 0x63C9,
+	/** [uinteger] A unique ID to identify the Chapter(s) the tags belong to. */
+	TagChapterUID = 0x63C4,
+	/** [uinteger] A unique ID to identify the Attachment(s) the tags belong to. */
+	TagAttachmentUID = 0x63C6,
+	/** [master] Contains general information about the target. */
+	SimpleTag = 0x67C8,
+	/** [utf-8] The name of the Tag that is going to be stored. */
+	TagName = 0x45A3,
+	/** [string] Specifies the language of the tag specified, in the Matroska languages form; see (#language-codes) on
+	   language codes. This Element **MUST** be ignored if the TagLanguageIETF Element is used within the same SimpleTag
+	   Element. */
+	TagLanguage = 0x447A,
+	/** [string] Specifies the language used in the TagString according to [@!BCP47] and using the IANA Language Subtag
+	   Registry [@!IANALangRegistry]. If this Element is used, then any TagLanguage Elements used in the same SimpleTag
+	   **MUST** be ignored. */
+	TagLanguageIETF = 0x447B,
+	/** [uinteger] A boolean value to indicate if this is the default/original language to use for the given tag. */
+	TagDefault = 0x4484,
+	/** [uinteger] A variant of the TagDefault element with a bogus Element ID; see (#tagdefault-element). */
+	TagDefaultBogus = 0x44B4,
+	/** [utf-8] The value of the Tag. */
+	TagString = 0x4487,
+	/** [binary] The values of the Tag, if it is binary. Note that this cannot be used in the same SimpleTag as
+	   TagString. */
+	TagBinary = 0x4485,
+};

+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/AC101AudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/AC101AudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/BufferedAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/BufferedAudioSink.h


+ 27 - 0
components/spotify/cspot/bell/include/audio/sinks/esp/ES8311AudioSink.h

@@ -0,0 +1,27 @@
+#ifndef ES8311AUDIOSINK_H
+#define ES8311AUDIOSINK_H
+
+#include "driver/i2s.h"
+#include <vector>
+#include <iostream>
+#include "BufferedAudioSink.h"
+#include <stdio.h>
+#include <string.h>
+#include "driver/gpio.h"
+#include "driver/i2c.h"
+#include <sys/unistd.h>
+#include <sys/stat.h>
+#include "esp_err.h"
+#include "esp_log.h"
+
+class ES8311AudioSink : public BufferedAudioSink
+{
+public:
+    ES8311AudioSink();
+    ~ES8311AudioSink();
+    void writeReg(uint8_t reg_add, uint8_t data);
+    void volumeChanged(uint16_t volume);
+private:
+};
+
+#endif

+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/ES8388AudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/ES8388AudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/ES9018AudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/ES9018AudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/InternalAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/InternalAudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/PCM5102AudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/PCM5102AudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/SPDIFAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/SPDIFAudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/TAS5711AudioSink.h → components/spotify/cspot/bell/include/audio/sinks/esp/TAS5711AudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/ac101.h → components/spotify/cspot/bell/include/audio/sinks/esp/ac101.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/esp/adac.h → components/spotify/cspot/bell/include/audio/sinks/esp/adac.h


+ 121 - 0
components/spotify/cspot/bell/include/audio/sinks/esp/es8311.h

@@ -0,0 +1,121 @@
+/*
+* ES8311.h  --  ES8311 ALSA SoC Audio Codec
+*
+* Authors:
+*
+* Based on ES8374.h by David Yang
+*
+* This program is free software; you can redistribute it and/or modify
+* it under the terms of the GNU General Public License version 2 as
+* published by the Free Software Foundation.
+*/
+
+#ifndef _ES8311_H
+#define _ES8311_H
+#include "driver/i2c.h"
+#include "esxxx_common.h"
+
+/*
+*   ES8311_REGISTER NAME_REG_REGISTER ADDRESS
+*/
+#define ES8311_RESET_REG00          0x00  /*reset digital,csm,clock manager etc.*/
+
+/*
+* Clock Scheme Register definition
+*/
+#define ES8311_CLK_MANAGER_REG01        0x01 /* select clk src for mclk, enable clock for codec */
+#define ES8311_CLK_MANAGER_REG02        0x02 /* clk divider and clk multiplier */
+#define ES8311_CLK_MANAGER_REG03        0x03 /* adc fsmode and osr  */
+#define ES8311_CLK_MANAGER_REG04        0x04 /* dac osr */
+#define ES8311_CLK_MANAGER_REG05        0x05 /* clk divier for adc and dac */
+#define ES8311_CLK_MANAGER_REG06        0x06 /* bclk inverter and divider */
+#define ES8311_CLK_MANAGER_REG07        0x07 /* tri-state, lrck divider */
+#define ES8311_CLK_MANAGER_REG08        0x08 /* lrck divider */
+#define ES8311_SDPIN_REG09              0x09 /* dac serial digital port */
+#define ES8311_SDPOUT_REG0A             0x0A /* adc serial digital port */
+#define ES8311_SYSTEM_REG0B             0x0B /* system */
+#define ES8311_SYSTEM_REG0C             0x0C /* system */
+#define ES8311_SYSTEM_REG0D             0x0D /* system, power up/down */
+#define ES8311_SYSTEM_REG0E             0x0E /* system, power up/down */
+#define ES8311_SYSTEM_REG0F             0x0F /* system, low power */
+#define ES8311_SYSTEM_REG10             0x10 /* system */
+#define ES8311_SYSTEM_REG11             0x11 /* system */
+#define ES8311_SYSTEM_REG12             0x12 /* system, Enable DAC */
+#define ES8311_SYSTEM_REG13             0x13 /* system */
+#define ES8311_SYSTEM_REG14             0x14 /* system, select DMIC, select analog pga gain */
+#define ES8311_ADC_REG15                0x15 /* ADC, adc ramp rate, dmic sense */
+#define ES8311_ADC_REG16                0x16 /* ADC */
+#define ES8311_ADC_REG17                0x17 /* ADC, volume */
+#define ES8311_ADC_REG18                0x18 /* ADC, alc enable and winsize */
+#define ES8311_ADC_REG19                0x19 /* ADC, alc maxlevel */
+#define ES8311_ADC_REG1A                0x1A /* ADC, alc automute */
+#define ES8311_ADC_REG1B                0x1B /* ADC, alc automute, adc hpf s1 */
+#define ES8311_ADC_REG1C                0x1C /* ADC, equalizer, hpf s2 */
+#define ES8311_DAC_REG31                0x31 /* DAC, mute */
+#define ES8311_DAC_REG32                0x32 /* DAC, volume */
+#define ES8311_DAC_REG33                0x33 /* DAC, offset */
+#define ES8311_DAC_REG34                0x34 /* DAC, drc enable, drc winsize */
+#define ES8311_DAC_REG35                0x35 /* DAC, drc maxlevel, minilevel */
+#define ES8311_DAC_REG37                0x37 /* DAC, ramprate */
+#define ES8311_GPIO_REG44               0x44 /* GPIO, dac2adc for test */
+#define ES8311_GP_REG45                 0x45 /* GP CONTROL */
+#define ES8311_CHD1_REGFD               0xFD /* CHIP ID1 */
+#define ES8311_CHD2_REGFE               0xFE /* CHIP ID2 */
+#define ES8311_CHVER_REGFF              0xFF /* VERSION */
+#define ES8311_CHD1_REGFD               0xFD /* CHIP ID1 */
+
+#define ES8311_MAX_REGISTER             0xFF
+
+
+typedef struct {
+    ESCodecMode esMode;
+    i2c_port_t i2c_port_num;
+    i2c_config_t i2c_cfg;
+    DacOutput dacOutput;
+    AdcInput adcInput;
+} Es8311Config;
+
+
+#define AUDIO_CODEC_ES8311_DEFAULT(){ \
+    .esMode = ES_MODE_SLAVE, \
+    .i2c_port_num = I2C_NUM_0, \
+    .i2c_cfg = { \
+        .mode = I2C_MODE_MASTER, \
+        .sda_io_num = IIC_DATA, \
+        .scl_io_num = IIC_CLK, \
+        .sda_pullup_en = GPIO_PULLUP_ENABLE,\
+        .scl_pullup_en = GPIO_PULLUP_ENABLE,\
+        .master.clk_speed = 100000\
+    }, \
+    .adcInput = ADC_INPUT_LINPUT1_RINPUT1,\
+    .dacOutput = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2,\
+};
+
+int Es8311Init(Es8311Config *cfg);
+void Es8311Uninit();
+esp_err_t Es8311GetRef(bool flag);
+esp_err_t Es7243Init(void);
+
+int Es7243ReadReg(uint8_t regAdd);
+
+int Es8311ConfigFmt(ESCodecModule mode, ESCodecI2SFmt fmt);
+int Es8311I2sConfigClock(ESCodecI2sClock cfg);
+int Es8311SetBitsPerSample(ESCodecModule mode, BitsLength bitPerSample);
+
+int Es8311Start(ESCodecModule mode);
+int Es8311Stop(ESCodecModule mode);
+
+int Es8311SetVoiceVolume(int volume);
+int Es8311GetVoiceVolume(int *volume);
+int Es8311SetVoiceMute(int enable);
+int Es8311GetVoiceMute(int *mute);
+int Es8311SetMicGain(MicGain gain);
+
+int Es8311ConfigAdcInput(AdcInput input);
+int Es8311ConfigDacOutput(DacOutput output);
+
+int ES8311WriteReg(uint8_t regAdd, uint8_t data);
+
+void Es8311ReadAll();
+int Es8311ReadReg(uint8_t regAdd);
+#endif

+ 166 - 0
components/spotify/cspot/bell/include/audio/sinks/esp/esxxx_common.h

@@ -0,0 +1,166 @@
+#ifndef __ESCODEC_COMMON_H__
+#define __ESCODEC_COMMON_H__
+
+typedef enum BitsLength {
+    BIT_LENGTH_MIN = -1,
+    BIT_LENGTH_16BITS = 0x03,
+    BIT_LENGTH_18BITS = 0x02,
+    BIT_LENGTH_20BITS = 0x01,
+    BIT_LENGTH_24BITS = 0x00,
+    BIT_LENGTH_32BITS = 0x04,
+    BIT_LENGTH_MAX,
+} BitsLength;
+
+typedef enum {
+    SAMPLE_RATE_MIN = -1,
+    SAMPLE_RATE_16K,
+    SAMPLE_RATE_32K,
+    SAMPLE_RATE_44_1K,
+    SAMPLE_RATE_MAX,
+} SampleRate;
+
+typedef enum {
+    MclkDiv_MIN = -1,
+    MclkDiv_1 = 1,
+    MclkDiv_2 = 2,
+    MclkDiv_3 = 3,
+    MclkDiv_4 = 4,
+    MclkDiv_6 = 5,
+    MclkDiv_8 = 6,
+    MclkDiv_9 = 7,
+    MclkDiv_11 = 8,
+    MclkDiv_12 = 9,
+    MclkDiv_16 = 10,
+    MclkDiv_18 = 11,
+    MclkDiv_22 = 12,
+    MclkDiv_24 = 13,
+    MclkDiv_33 = 14,
+    MclkDiv_36 = 15,
+    MclkDiv_44 = 16,
+    MclkDiv_48 = 17,
+    MclkDiv_66 = 18,
+    MclkDiv_72 = 19,
+    MclkDiv_5 = 20,
+    MclkDiv_10 = 21,
+    MclkDiv_15 = 22,
+    MclkDiv_17 = 23,
+    MclkDiv_20 = 24,
+    MclkDiv_25 = 25,
+    MclkDiv_30 = 26,
+    MclkDiv_32 = 27,
+    MclkDiv_34 = 28,
+    MclkDiv_7  = 29,
+    MclkDiv_13 = 30,
+    MclkDiv_14 = 31,
+    MclkDiv_MAX,
+} SclkDiv;
+
+typedef enum {
+    LclkDiv_MIN = -1,
+    LclkDiv_128 = 0,
+    LclkDiv_192 = 1,
+    LclkDiv_256 = 2,
+    LclkDiv_384 = 3,
+    LclkDiv_512 = 4,
+    LclkDiv_576 = 5,
+    LclkDiv_768 = 6,
+    LclkDiv_1024 = 7,
+    LclkDiv_1152 = 8,
+    LclkDiv_1408 = 9,
+    LclkDiv_1536 = 10,
+    LclkDiv_2112 = 11,
+    LclkDiv_2304 = 12,
+
+    LclkDiv_125 = 16,
+    LclkDiv_136 = 17,
+    LclkDiv_250 = 18,
+    LclkDiv_272 = 19,
+    LclkDiv_375 = 20,
+    LclkDiv_500 = 21,
+    LclkDiv_544 = 22,
+    LclkDiv_750 = 23,
+    LclkDiv_1000 = 24,
+    LclkDiv_1088 = 25,
+    LclkDiv_1496 = 26,
+    LclkDiv_1500 = 27,
+    LclkDiv_MAX,
+} LclkDiv;
+
+typedef enum {
+    ADC_INPUT_MIN = -1,
+    ADC_INPUT_LINPUT1_RINPUT1 = 0x00,
+    ADC_INPUT_MIC1  = 0x05,
+    ADC_INPUT_MIC2  = 0x06,
+    ADC_INPUT_LINPUT2_RINPUT2 = 0x50,
+    ADC_INPUT_DIFFERENCE = 0xf0,
+    ADC_INPUT_MAX,
+} AdcInput;
+
+typedef enum {
+    DAC_OUTPUT_MIN = -1,
+    DAC_OUTPUT_LOUT1 = 0x04,
+    DAC_OUTPUT_LOUT2 = 0x08,
+    DAC_OUTPUT_SPK   = 0x09,
+    DAC_OUTPUT_ROUT1 = 0x10,
+    DAC_OUTPUT_ROUT2 = 0x20,
+    DAC_OUTPUT_ALL = 0x3c,
+    DAC_OUTPUT_MAX,
+} DacOutput;
+
+typedef enum {
+    D2SE_PGA_GAIN_MIN = -1,
+    D2SE_PGA_GAIN_DIS = 0,
+    D2SE_PGA_GAIN_EN = 1,
+    D2SE_PGA_GAIN_MAX = 2,
+} D2SEPGA;
+
+typedef enum {
+    MIC_GAIN_MIN = -1,
+    MIC_GAIN_0DB = 0,
+    MIC_GAIN_3DB = 3,
+    MIC_GAIN_6DB = 6,
+    MIC_GAIN_9DB = 9,
+    MIC_GAIN_12DB = 12,
+    MIC_GAIN_15DB = 15,
+    MIC_GAIN_18DB = 18,
+    MIC_GAIN_21DB = 21,
+    MIC_GAIN_24DB = 24,
+#if defined CONFIG_CODEC_CHIP_IS_ES8311
+    MIC_GAIN_30DB = 30,
+    MIC_GAIN_36DB = 36,
+    MIC_GAIN_42DB = 42,
+#endif
+    MIC_GAIN_MAX,
+} MicGain;
+
+typedef enum {
+    ES_MODULE_MIN = -1,
+    ES_MODULE_ADC = 0x01,
+    ES_MODULE_DAC = 0x02,
+    ES_MODULE_ADC_DAC = 0x03,
+    ES_MODULE_LINE = 0x04,
+    ES_MODULE_MAX
+} ESCodecModule;
+
+typedef enum {
+    ES_MODE_MIN = -1,
+    ES_MODE_SLAVE = 0x00,
+    ES_MODE_MASTER = 0x01,
+    ES_MODE_MAX,
+} ESCodecMode;
+
+typedef enum {
+    ES_ = -1,
+    ES_I2S_NORMAL = 0,
+    ES_I2S_LEFT = 1,
+    ES_I2S_RIGHT = 2,
+    ES_I2S_DSP = 3,
+    ES_I2S_MAX
+} ESCodecI2SFmt;
+
+typedef struct {
+    SclkDiv sclkDiv;
+    LclkDiv lclkDiv;
+} ESCodecI2sClock;
+
+#endif //__ESCODEC_COMMON_H__

+ 1 - 1
components/spotify/cspot/bell/include/sinks/unix/ALSAAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/unix/ALSAAudioSink.h

@@ -5,7 +5,7 @@
 #include "AudioSink.h"
 #include <alsa/asoundlib.h>
 #include <stdio.h>
-#include <Task.h>
+#include <BellTask.h>
 #include <unistd.h>
 #include <memory>
 #include <mutex>

+ 0 - 0
components/spotify/cspot/bell/include/sinks/unix/NamedPipeAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/unix/NamedPipeAudioSink.h


+ 0 - 0
components/spotify/cspot/bell/include/sinks/unix/PortAudioSink.h → components/spotify/cspot/bell/include/audio/sinks/unix/PortAudioSink.h


+ 18 - 0
components/spotify/cspot/bell/include/platform/MDNSService.h

@@ -0,0 +1,18 @@
+#ifndef BELLL_MDNS_SERVICE_H
+#define BELLL_MDNS_SERVICE_H
+#include <string>
+#include <map>
+
+class MDNSService {
+public:
+    static void registerService(
+        const std::string &serviceName,
+        const std::string &serviceType,
+        const std::string &serviceProto,
+        const std::string &serviceHost,
+        int servicePort,
+        const std::map<std::string, std::string> txtData
+    );
+};
+
+#endif

+ 4 - 0
components/spotify/cspot/bell/include/platform/WrappedSemaphore.h

@@ -6,6 +6,8 @@
 #include "freertos/semphr.h"
 #elif __APPLE__
 #include <dispatch/dispatch.h>
+#elif _WIN32
+#include <winsock2.h>
 #else
 #include <time.h>
 #include <semaphore.h>
@@ -18,6 +20,8 @@ private:
     xSemaphoreHandle semaphoreHandle;
 #elif __APPLE__
     dispatch_semaphore_t semaphoreHandle;
+#elif _WIN32
+    HANDLE semaphoreHandle;
 #else
     sem_t semaphoreHandle;
 #endif

+ 15 - 0
components/spotify/cspot/bell/include/platform/win32/win32shim.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include <winsock2.h>
+
+#define SHUT_RDWR SD_BOTH
+#define ssize_t SSIZE_T
+
+#define strcasecmp stricmp
+#define strncasecmp _strnicmp
+#define bzero(p,n) memset(p,0,n)
+#define usleep(x) Sleep((x)/1000)
+
+inline void close(int sock) { closesocket(sock); }
+inline size_t read(int sock, char* buf, size_t n) { return recv(sock, buf, n, 0); }
+inline int write(int sock, const char* buf, size_t n) { return send(sock, buf, n, 0); }

+ 39 - 0
components/spotify/cspot/bell/nanopb/.github/workflows/cifuzz.yml

@@ -0,0 +1,39 @@
+name: CIFuzz
+on:
+  push:
+    branches:
+      - master
+    paths:
+      - '**.c'
+      - '**.h'
+  pull_request:
+    branches:
+      - master
+    paths:
+      - '**.c'
+      - '**.h'
+
+jobs:
+  Fuzzing:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Build Fuzzers
+        id: build
+        uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+        with:
+          oss-fuzz-project-name: 'nanopb'
+          dry-run: false
+          sanitizer: undefined
+      - name: Run Fuzzers
+        uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+        with:
+          oss-fuzz-project-name: 'nanopb'
+          fuzz-seconds: 600
+          dry-run: false
+          sanitizer: undefined
+      - name: Upload Crash
+        uses: actions/upload-artifact@v1
+        if: failure() && steps.build.outcome == 'success'
+        with:
+          name: artifacts
+          path: ./out/artifacts

+ 63 - 0
components/spotify/cspot/bell/nanopb/.github/workflows/platformio.yaml

@@ -0,0 +1,63 @@
+name: platformio
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  platformio:
+    name: Build and run PlatformIO example
+    runs-on: ubuntu-latest
+    steps:
+      - name: ⤵️ Check out code from GitHub
+        uses: actions/checkout@v2
+        with:
+          path: nanopb
+
+      - name: Installing dependencies for local act
+        if: ${{ env.ACT }}
+        run: |
+          sudo apt update
+
+      - name: Installing common dependencies
+        run: |
+          sudo apt install -y python3-pip
+
+      - name: Install and setup PlatformIO
+        run: |
+          pip3 install -U platformio
+          export PATH=~/.local/bin:$PATH
+
+      - name: Build PlatformIO package
+        run: |
+          cd nanopb
+          pio package pack
+
+      - name: Example - Extract PlatformIO package to example dir
+        run: |
+          cp -R nanopb/examples/platformio example
+          mkdir -p example/lib/nanopb
+          tar -xzf nanopb/Nanopb-*.tar.gz -C example/lib/nanopb
+
+      - name: Example - Build
+        run: |
+          cd example
+          pio run
+
+      - name: Example - Run test without options
+        run: example/.pio/build/pio_without_options/program
+
+      - name: Example - Run test with options
+        run: example/.pio/build/pio_with_options/program
+
+      - name: Build with default platformio.ini
+        run: |
+          mkdir -p test_default_pio_conf
+          cd test_default_pio_conf
+          pio project init
+          ln -s ../nanopb lib/nanopb
+          echo "[env:native]" >> platformio.ini
+          echo "platform = native" >> platformio.ini
+          echo "lib_deps = Nanopb" >> platformio.ini
+          echo "int main(int argc, char *argv[]){}" > src/main.cpp
+          pio run

+ 15 - 0
components/spotify/cspot/bell/nanopb/.github/workflows/spm.yml

@@ -0,0 +1,15 @@
+name: spm
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  swift-build-run:
+    runs-on: macOS-latest
+    steps:
+    - uses: actions/checkout@v2
+    - name: Build
+      run: swift build
+    - name: Run
+      run: swift test

+ 16 - 0
components/spotify/cspot/bell/nanopb/AUTHORS.txt

@@ -98,3 +98,19 @@ leabut <leabut@users.noreply.github.com>
 Angel ILIEV <a.v.iliev13@gmail.com>
 Jakub Tymejczyk <jakub@tymejczyk.pl>
 Matthew Simmons <simmonmt@acm.org>
+Anthony Pesch <inolen@gmail.com>
+Avik De <avikde@gmail.com>
+ConradWood <github@conradwood.net>
+David Sabatie <david.sabatie@notrenet.com>
+Sebastian Stockhammer <sebastian.stockhammer@rosenberger.de>
+Gil Shapira <gil.shapira@intusurg.com>
+Ian Frosst <ianjfrosst@gmail.com>
+Ingo Kresse <ingo.kresse@kuka.com>
+Ivan Zrno <ivan.zrno2@gmail.com>
+Jonathan Seilkopf <j.seilkopf@isatech.de>
+Karl Ljungkvist <k.ljungkvist@gmail.com>
+Mathis Logemann <mathisloge@gmail.com>
+Oleg Dolgy <60554929+odolgy@users.noreply.github.com>
+Pavel Sokolov <pavel@sokolov.me>
+Slavey Karadzhov <slav@attachix.com>
+Tobias Nießen <tniessen@tnie.de>

+ 47 - 0
components/spotify/cspot/bell/nanopb/CHANGELOG.txt

@@ -1,3 +1,43 @@
+nanopb-0.4.6 (2022-05-30)
+ Fix passing of error message from substream callback (#703)
+ Fix comments going to wrong member variables (#701)
+ Fix regression in 0.4.3 where generator did not find all dependencies (#720)
+ Fix FindNanopb.cmake not finding options file (#659)
+ Fix double-definition errors with size_union (#692)
+ Fix generator error with same inner message name (#746)
+ Fix infinite recursion in generator/protoc script (#762)
+ Fix unicode comment handling for Python 2 (#740)
+ Fix compiler warnings with PB_BUFFER_ONLY (#717)
+ Fix options dependency in nanopb.mk (#666)
+ Fix handling of filenames with dot in them in FindNanopb.cmake (#756)
+ Add fallback_type option (#772, #773)
+ Use C11 static assert mechanism by default (#761, #766)
+ Use 'static_assert'  keyword for iar (#679)
+ Explicitly check for pItem == NULL to satisfy Xcode analyzer (#667, #674)
+ Support --proto-path as alias to -I (#749)
+ Refactor name mangling to separate class, improve error messages (#735)
+ Move PB_WT_PACKED definition to the header to fix compiler warnings (#671)
+ FindNanopb.cmake: use --nanopb_opt for option passing by default (#752)
+ FindNanopb.cmake: Add option NANOPB_GENERATE_CPP_STANDALONE (#741)
+ FindNanopb.cmake: Add PROTOC_OPTIONS variable (#768, #771)
+ CMakeLists: add build interface for using as a submodule (#669)
+ CMakeLists: fix error with nanopb_BUILD_GENERATOR=OFF (#764)
+ CMakeLists: make more uniform (#676)
+ CMakeLists: Fix uninitialized PYTHON_INSTDIR (#652)
+ Clean up CMake examples (#741)
+ Rebuild nanopb_pb2.py and print version numbers on import failure (#733, #742)
+ Use memcpy instead of iterating on buf_read/write (#751)
+ Add generator support for PlatformIO (#718)
+ Add clean target to generator/proto/Makefile (#681)
+ Windows .bats: use standard python invocation instead of py.exe launcher (#657)
+ Fix problems running tests with newer SCons version
+ Improve handling of varint overflows
+ Improve optimization for little-endian platforms
+
+NOTE: During development, prereleases were published on PlatformIO registry
+as versions 0.4.6 - 0.4.6.3. The version 0.4.6.4 on PlatformIO corresponds
+to the real final 0.4.6 release.
+
 nanopb-0.4.5 (2021-03-22)
  Fix invalid free() with oneof (#647, GHSA-7mv5-5mxh-qg88)
  Fix unordered field numbers inside oneof causing fields to be ignored (#617)
@@ -149,6 +189,13 @@ nanopb-0.4.0 (2019-12-20)
  CMake: Split nanopb_out command (#454)
  CMake: install created shared library(dll) in windows to the binary folder (#447)
 
+nanopb-0.3.9.9 (2022-04-23)
+ Fix Xcode analyzer warnings (#667, #674)
+ Fix clang sanitizer warnings
+
+Note: there are no known functional differences between 0.3.9.8 and 0.3.9.9.
+The changes are merely to fix warnings introduced by new compiler versions.
+
 nanopb-0.3.9.8 (2021-03-22)
  Fix invalid free() with oneof (#647, GHSA-7mv5-5mxh-qg88)
  Don't generate lines with trailing spaces (#622)

+ 5 - 5
components/spotify/cspot/bell/nanopb/CMakeLists.txt

@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 2.8.12)
 
 project(nanopb C)
 
-set(nanopb_VERSION_STRING nanopb-0.4.6-dev)
+set(nanopb_VERSION_STRING nanopb-0.4.7-dev)
 set(nanopb_SOVERSION 0)
 
 string(REPLACE "nanopb-" "" nanopb_VERSION ${nanopb_VERSION_STRING})
@@ -65,10 +65,10 @@ if(nanopb_BUILD_GENERATOR)
             DESTINATION ${PYTHON_INSTDIR}/proto/
         )
     endforeach()
-endif()
 
-install(FILES generator/proto/_utils.py
-        DESTINATION ${PYTHON_INSTDIR}/proto/)
+    install( FILES generator/proto/_utils.py
+             DESTINATION ${PYTHON_INSTDIR}/proto/ )
+endif()
 
 if(WIN32)
     install(
@@ -123,7 +123,7 @@ if(nanopb_BUILD_RUNTIME)
             ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
         target_include_directories(protobuf-nanopb-static INTERFACE
             $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
-	        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+            $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
         )
     endif()
 

+ 0 - 3
components/spotify/cspot/bell/nanopb/examples/cmake_relpath/CMakeLists.txt

@@ -9,9 +9,6 @@ nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS RELPATH proto
 	proto/simple.proto proto/sub/unlucky.proto)
 
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
-#add_custom_target(generate_proto_sources DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
-set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS}
-    PROPERTIES GENERATED TRUE)
 
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -g -O0")
 

+ 0 - 3
components/spotify/cspot/bell/nanopb/examples/cmake_simple/CMakeLists.txt

@@ -7,9 +7,6 @@ include_directories(${NANOPB_INCLUDE_DIRS})
 
 nanopb_generate_cpp(PROTO_SRCS PROTO_HDRS simple.proto)
 include_directories(${CMAKE_CURRENT_BINARY_DIR})
-#add_custom_target(generate_proto_sources DEPENDS ${PROTO_SRCS} ${PROTO_HDRS})
-set_source_files_properties(${PROTO_SRCS} ${PROTO_HDRS}
-    PROPERTIES GENERATED TRUE)
 
 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -g -O0")
 

+ 24 - 5
components/spotify/cspot/bell/nanopb/pb.h

@@ -52,6 +52,11 @@
  * Normally it is automatically detected based on __BYTE_ORDER__ macro. */
 /* #define PB_LITTLE_ENDIAN_8BIT 1 */
 
+/* Configure static assert mechanism. Instead of changing these, set your
+ * compiler to C11 standard mode if possible. */
+/* #define PB_C99_STATIC_ASSERT 1 */
+/* #define PB_NO_STATIC_ASSERT 1 */
+
 /******************************************************************
  * You usually don't need to change anything below this line.     *
  * Feel free to look around and use the defined macros, though.   *
@@ -60,7 +65,7 @@
 
 /* Version of the nanopb library. Just in case you want to check it in
  * your own program. */
-#define NANOPB_VERSION "nanopb-0.4.6-dev"
+#define NANOPB_VERSION "nanopb-0.4.7-dev"
 
 /* Include all the system headers needed by nanopb. You will need the
  * definitions of the following:
@@ -165,14 +170,17 @@ extern "C" {
 #    if defined(__ICCARM__)
        /* IAR has static_assert keyword but no _Static_assert */
 #      define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG);
-#    elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
-       /* C11 standard _Static_assert mechanism */
-#      define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG);
-#    else
+#    elif defined(PB_C99_STATIC_ASSERT)
        /* Classic negative-size-array static assert mechanism */
 #      define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1];
 #      define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
 #      define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER
+#    elif defined(__cplusplus)
+       /* C++11 standard static_assert mechanism */
+#      define PB_STATIC_ASSERT(COND,MSG) static_assert(COND,#MSG);
+#    else
+       /* C11 standard _Static_assert mechanism */
+#      define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG);
 #    endif
 #  endif
 #else
@@ -180,6 +188,14 @@ extern "C" {
 #  define PB_STATIC_ASSERT(COND,MSG)
 #endif
 
+/* Test that PB_STATIC_ASSERT works
+ * If you get errors here, you may need to do one of these:
+ * - Enable C11 standard support in your compiler
+ * - Define PB_C99_STATIC_ASSERT to enable C99 standard support
+ * - Define PB_NO_STATIC_ASSERT to disable static asserts altogether
+ */
+PB_STATIC_ASSERT(1, STATIC_ASSERT_IS_NOT_WORKING)
+
 /* Number of required fields to keep track of. */
 #ifndef PB_MAX_REQUIRED_FIELDS
 #define PB_MAX_REQUIRED_FIELDS 64
@@ -886,10 +902,13 @@ struct pb_extension_s {
 #define PB_INLINE_CONSTEXPR PB_CONSTEXPR
 #endif  // __cplusplus >= 201703L
 
+extern "C++"
+{
 namespace nanopb {
 // Each type will be partially specialized by the generator.
 template <typename GenMessageT> struct MessageDescriptor;
 }  // namespace nanopb
+}
 #endif  /* __cplusplus */
 
 #endif

+ 16 - 13
components/spotify/cspot/bell/nanopb/pb_decode.c

@@ -67,14 +67,12 @@ typedef struct {
 
 static bool checkreturn buf_read(pb_istream_t *stream, pb_byte_t *buf, size_t count)
 {
-    size_t i;
     const pb_byte_t *source = (const pb_byte_t*)stream->state;
     stream->state = (pb_byte_t*)stream->state + count;
     
     if (buf != NULL)
     {
-        for (i = 0; i < count; i++)
-            buf[i] = source[i];
+        memcpy(buf, source, count * sizeof(pb_byte_t));
     }
     
     return true;
@@ -211,18 +209,20 @@ static bool checkreturn pb_decode_varint32_eof(pb_istream_t *stream, uint32_t *d
                     PB_RETURN_ERROR(stream, "varint overflow");
                 }
             }
+            else if (bitpos == 28)
+            {
+                if ((byte & 0x70) != 0 && (byte & 0x78) != 0x78)
+                {
+                    PB_RETURN_ERROR(stream, "varint overflow");
+                }
+                result |= (uint32_t)(byte & 0x0F) << bitpos;
+            }
             else
             {
                 result |= (uint32_t)(byte & 0x7F) << bitpos;
             }
             bitpos = (uint_fast8_t)(bitpos + 7);
         } while (byte & 0x80);
-        
-        if (bitpos == 35 && (byte & 0x70) != 0)
-        {
-            /* The last byte was at bitpos=28, so only bottom 4 bits fit. */
-            PB_RETURN_ERROR(stream, "varint overflow");
-        }
    }
    
    *dest = result;
@@ -243,12 +243,12 @@ bool checkreturn pb_decode_varint(pb_istream_t *stream, uint64_t *dest)
     
     do
     {
-        if (bitpos >= 64)
-            PB_RETURN_ERROR(stream, "varint overflow");
-        
         if (!pb_readbyte(stream, &byte))
             return false;
 
+        if (bitpos >= 63 && (byte & 0xFE) != 0)
+            PB_RETURN_ERROR(stream, "varint overflow");
+
         result |= (uint64_t)(byte & 0x7F) << bitpos;
         bitpos = (uint_fast8_t)(bitpos + 7);
     } while (byte & 0x80);
@@ -761,7 +761,10 @@ static bool checkreturn decode_callback_field(pb_istream_t *stream, pb_wire_type
         {
             prev_bytes_left = substream.bytes_left;
             if (!field->descriptor->field_callback(&substream, NULL, field))
-                PB_RETURN_ERROR(stream, "callback failed");
+            {
+                PB_SET_ERROR(stream, substream.errmsg ? substream.errmsg : "callback failed");
+                return false;
+            }
         } while (substream.bytes_left > 0 && substream.bytes_left < prev_bytes_left);
         
         if (!pb_close_string_substream(stream, &substream))

+ 3 - 4
components/spotify/cspot/bell/nanopb/pb_encode.c

@@ -51,12 +51,10 @@ static bool checkreturn pb_enc_fixed_length_bytes(pb_ostream_t *stream, const pb
 
 static bool checkreturn buf_write(pb_ostream_t *stream, const pb_byte_t *buf, size_t count)
 {
-    size_t i;
     pb_byte_t *dest = (pb_byte_t*)stream->state;
     stream->state = dest + count;
     
-    for (i = 0; i < count; i++)
-        dest[i] = buf[i];
+    memcpy(dest, buf, count * sizeof(pb_byte_t));
     
     return true;
 }
@@ -626,8 +624,9 @@ bool checkreturn pb_encode_varint(pb_ostream_t *stream, pb_uint64_t value)
 bool checkreturn pb_encode_svarint(pb_ostream_t *stream, pb_int64_t value)
 {
     pb_uint64_t zigzagged;
+    pb_uint64_t mask = ((pb_uint64_t)-1) >> 1; /* Satisfy clang -fsanitize=integer */
     if (value < 0)
-        zigzagged = ~((pb_uint64_t)value << 1);
+        zigzagged = ~(((pb_uint64_t)value & mask) << 1);
     else
         zigzagged = (pb_uint64_t)value << 1;
     

+ 1 - 1
components/spotify/cspot/bell/nanopb/tests/site_scons/platforms/avr/run_test.c

@@ -146,7 +146,7 @@ int main(int argc, char *argv[])
         filename = argv[2];
     }
     
-    elf_firmware_t firmware;
+    elf_firmware_t firmware = {};
     elf_read_firmware(filename, &firmware);
     avr_init(g_avr);
 	avr_load_firmware(g_avr, &firmware);

二进制
components/spotify/cspot/bell/src/.DS_Store


+ 0 - 88
components/spotify/cspot/bell/src/BaseHTTPServer.cpp0

@@ -1,88 +0,0 @@
-#include "BaseHTTPServer.h"
-#include <sstream>
-
-unsigned char bell::BaseHTTPServer::h2int(char c)
-{
-    if (c >= '0' && c <= '9')
-    {
-        return ((unsigned char)c - '0');
-    }
-    if (c >= 'a' && c <= 'f')
-    {
-        return ((unsigned char)c - 'a' + 10);
-    }
-    if (c >= 'A' && c <= 'F')
-    {
-        return ((unsigned char)c - 'A' + 10);
-    }
-    return (0);
-}
-
-std::string bell::BaseHTTPServer::urlDecode(std::string str)
-{
-    std::string encodedString = "";
-    char c;
-    char code0;
-    char code1;
-    for (int i = 0; i < str.length(); i++)
-    {
-        c = str[i];
-        if (c == '+')
-        {
-            encodedString += ' ';
-        }
-        else if (c == '%')
-        {
-            i++;
-            code0 = str[i];
-            i++;
-            code1 = str[i];
-            c = (h2int(code0) << 4) | h2int(code1);
-            encodedString += c;
-        }
-        else
-        {
-
-            encodedString += c;
-        }
-    }
-
-    return encodedString;
-}
-
-std::vector<std::string> bell::BaseHTTPServer::splitUrl(const std::string &url, char delimiter)
-{
-    std::stringstream ssb(url);
-    std::string segment;
-    std::vector<std::string> seglist;
-
-    while (std::getline(ssb, segment, delimiter))
-    {
-        seglist.push_back(segment);
-    }
-    return seglist;
-}
-
-std::map<std::string, std::string> bell::BaseHTTPServer::parseQueryString(const std::string &queryString)
-{
-    std::map<std::string, std::string> query;
-    auto prefixedString = "&" + queryString;
-    while (prefixedString.find("&") != std::string::npos)
-    {
-        auto keyStart = prefixedString.find("&");
-        auto keyEnd = prefixedString.find("=");
-        // Find second occurence of "&" in prefixedString
-        auto valueEnd = prefixedString.find("&", keyStart + 1);
-        if (valueEnd == std::string::npos)
-        {
-            valueEnd = prefixedString.size();
-        }
-
-        auto key = prefixedString.substr(keyStart + 1, keyEnd - 1);
-        auto value = prefixedString.substr(keyEnd + 1, valueEnd - keyEnd - 1);
-        query[key] = urlDecode(value);
-        prefixedString = prefixedString.substr(valueEnd);
-    }
-
-    return query;
-}

+ 2 - 2
components/spotify/cspot/bell/src/BellLogger.cpp

@@ -1,9 +1,9 @@
 #include "BellLogger.h"
 
-std::shared_ptr<bell::AbstractLogger> bell::bellGlobalLogger;
+bell::AbstractLogger* bell::bellGlobalLogger;
 
 void bell::setDefaultLogger() {
-    bell::bellGlobalLogger = std::make_shared<bell::BellLogger>();
+    bell::bellGlobalLogger = new bell::BellLogger();
 }
 
 void bell::enableSubmoduleLogging() {

+ 2 - 2
components/spotify/cspot/bell/src/BinaryReader.cpp

@@ -18,8 +18,8 @@ void bell::BinaryReader::close() {
 }
 
 void bell::BinaryReader::skip(size_t pos) {
-    uint8_t b[pos];
-    stream->read((uint8_t *)b, pos);
+    std::vector<uint8_t> b(pos);
+    stream->read(&b[0], pos);
 }
 
 int32_t bell::BinaryReader::readInt() {

+ 174 - 0
components/spotify/cspot/bell/src/BufferedStream.cpp

@@ -0,0 +1,174 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-7.
+
+#include "BufferedStream.h"
+#include <cstring>
+
+BufferedStream::BufferedStream(
+	const std::string &taskName,
+	uint32_t bufferSize,
+	uint32_t readThreshold,
+	uint32_t readSize,
+	uint32_t readyThreshold,
+	uint32_t notReadyThreshold,
+	bool waitForReady)
+	: bell::Task(taskName, 4096, 5, 0) {
+	this->bufferSize = bufferSize;
+	this->readAt = bufferSize - readThreshold;
+	this->readSize = readSize;
+	this->readyThreshold = readyThreshold;
+	this->notReadyThreshold = notReadyThreshold;
+	this->waitForReady = waitForReady;
+	this->buf = static_cast<uint8_t *>(malloc(bufferSize));
+	this->bufEnd = buf + bufferSize;
+	reset();
+}
+
+BufferedStream::~BufferedStream() {
+	this->close();
+	free(buf);
+}
+
+void BufferedStream::close() {
+	this->terminate = true;
+	this->readSem.give(); // force a read operation
+	const std::lock_guard lock(runningMutex);
+	if (this->source)
+		this->source->close();
+	this->source = nullptr;
+}
+
+void BufferedStream::reset() {
+	this->bufReadPtr = this->buf;
+	this->bufWritePtr = this->buf;
+	this->readTotal = 0;
+	this->bufferTotal = 0;
+	this->readAvailable = 0;
+	this->terminate = false;
+}
+
+bool BufferedStream::open(const std::shared_ptr<bell::ByteStream> &stream) {
+	if (this->running)
+		this->close();
+	reset();
+	this->source = stream;
+	startTask();
+	return source.get();
+}
+
+bool BufferedStream::open(const StreamReader &newReader, uint32_t initialOffset) {
+	if (this->running)
+		this->close();
+	reset();
+	this->reader = newReader;
+	this->bufferTotal = initialOffset;
+	startTask();
+	return source.get();
+}
+
+bool BufferedStream::isReady() const {
+	return readAvailable >= readyThreshold;
+}
+
+bool BufferedStream::isNotReady() const {
+	return readAvailable < notReadyThreshold;
+}
+
+size_t BufferedStream::skip(size_t len) {
+	return read(nullptr, len);
+}
+
+size_t BufferedStream::position() {
+	return readTotal;
+}
+
+size_t BufferedStream::size() {
+	return source->size();
+}
+
+uint32_t BufferedStream::lengthBetween(uint8_t *me, uint8_t *other) {
+	const std::lock_guard lock(readMutex);
+	if (other <= me) {
+		// buf .... other ...... me ........ bufEnd
+		// buf .... me/other ........ bufEnd
+		return bufEnd - me;
+	} else {
+		// buf ........ me ........ other .... bufEnd
+		return other - me;
+	}
+}
+
+size_t BufferedStream::read(uint8_t *dst, size_t len) {
+	if (waitForReady && isNotReady()) {
+		while ((source || reader) && !isReady()) {} // end waiting after termination
+	}
+	if (!running && !readAvailable) {
+		reset();
+		return 0;
+	}
+	uint32_t read = 0;
+	uint32_t toReadTotal = std::min(readAvailable.load(), static_cast<uint32_t>(len));
+	while (toReadTotal > 0) {
+		uint32_t toRead = std::min(toReadTotal, lengthBetween(bufReadPtr, bufWritePtr));
+		if (dst) {
+			memcpy(dst, bufReadPtr, toRead);
+			dst += toRead;
+		}
+		readAvailable -= toRead;
+		bufReadPtr += toRead;
+		if (bufReadPtr >= bufEnd)
+			bufReadPtr = buf;
+		toReadTotal -= toRead;
+		read += toRead;
+		readTotal += toRead;
+	}
+	this->readSem.give();
+	return read;
+}
+
+void BufferedStream::runTask() {
+	const std::lock_guard lock(runningMutex);
+	running = true;
+	if (!source && reader) {
+		// get the initial request on the task's thread
+		source = reader(this->bufferTotal);
+	}
+	while (!terminate) {
+		if (!source)
+			break;
+		if (isReady()) {
+			// buffer ready, wait for any read operations
+			this->readSem.wait();
+		}
+		if (terminate)
+			break;
+		if (readAvailable > readAt)
+			continue;
+		// here, the buffer needs re-filling
+		uint32_t len;
+		bool wasReady = isReady();
+		do {
+			uint32_t toRead = std::min(readSize, lengthBetween(bufWritePtr, bufReadPtr));
+			if (!source) {
+				len = 0;
+				break;
+			}
+			len = source->read(bufWritePtr, toRead);
+			readAvailable += len;
+			bufferTotal += len;
+			bufWritePtr += len;
+			if (bufWritePtr >= bufEnd) // TODO is == enough here?
+				bufWritePtr = buf;
+		} while (len && readSize < bufferSize - readAvailable); // loop until there's no more free space in the buffer
+		if (!len && reader)
+			source = reader(bufferTotal);
+		else if (!len)
+			terminate = true;
+		// signal that buffer is ready for reading
+		if (!wasReady && isReady()) {
+			this->readySem.give();
+		}
+	}
+	source = nullptr;
+	reader = nullptr;
+	running = false;
+}

+ 1 - 3
components/spotify/cspot/bell/src/CryptoMBedTLS.cpp → components/spotify/cspot/bell/src/Crypto.cpp

@@ -1,5 +1,4 @@
-#ifdef BELL_USE_MBEDTLS
-#include "CryptoMbedTLS.h"
+#include "Crypto.h"
 
 CryptoMbedTLS::CryptoMbedTLS()
 {
@@ -224,4 +223,3 @@ std::vector<uint8_t> CryptoMbedTLS::generateVectorWithRandomData(size_t length)
 
     return randomVector;
 }
-#endif

+ 0 - 184
components/spotify/cspot/bell/src/CryptoOpenSSL.cpp

@@ -1,184 +0,0 @@
-#include "CryptoOpenSSL.h"
-namespace
-{
-    struct BIOFreeAll
-    {
-        void operator()(BIO *p) { BIO_free_all(p); }
-    };
-} // namespace
-
-CryptoOpenSSL::CryptoOpenSSL()
-{
-    // OpenSSL init
-    ENGINE_load_builtin_engines();
-    ENGINE_register_all_complete();
-    this->publicKey = std::vector<uint8_t>(DH_KEY_SIZE);
-    this->privateKey = generateVectorWithRandomData(DH_KEY_SIZE);
-}
-
-CryptoOpenSSL::~CryptoOpenSSL()
-{
-    if (this->dhContext != nullptr)
-    {
-        DH_free(this->dhContext);
-    }
-}
-
-std::vector<uint8_t> CryptoOpenSSL::base64Decode(const std::string& data)
-{
-    // base64 in openssl is an absolute mess lmao
-    std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
-    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
-    BIO *source = BIO_new_mem_buf(data.c_str(), -1); // read-only source
-    BIO_push(b64.get(), source);
-    const int maxlen = data.size() / 4 * 3 + 1;
-    std::vector<uint8_t> decoded(maxlen);
-    const int len = BIO_read(b64.get(), decoded.data(), maxlen);
-    decoded.resize(len);
-    return decoded;
-}
-
-std::string CryptoOpenSSL::base64Encode(const std::vector<uint8_t>& data)
-{
-    // base64 in openssl is an absolute mess lmao x 2
-    std::unique_ptr<BIO, BIOFreeAll> b64(BIO_new(BIO_f_base64()));
-
-    // No newline mode, put all the data into sink
-    BIO_set_flags(b64.get(), BIO_FLAGS_BASE64_NO_NL);
-    BIO *sink = BIO_new(BIO_s_mem());
-    BIO_push(b64.get(), sink);
-    BIO_write(b64.get(), data.data(), data.size());
-    BIO_flush(b64.get());
-    const char *encoded;
-    const long len = BIO_get_mem_data(sink, &encoded);
-    return std::string(encoded, len);
-}
-
-// Sha1
-void CryptoOpenSSL::sha1Init()
-{
-    SHA1_Init(&sha1Context);
-}
-
-void CryptoOpenSSL::sha1Update(const std::string& s)
-{
-    sha1Update(std::vector<uint8_t>(s.begin(), s.end()));
-}
-void CryptoOpenSSL::sha1Update(const std::vector<uint8_t>& vec)
-{
-    SHA1_Update(&sha1Context, vec.data(), vec.size());
-}
-
-std::vector<uint8_t> CryptoOpenSSL::sha1FinalBytes()
-{
-    std::vector<uint8_t> digest(20); // 20 is 160 bits
-    SHA1_Final(digest.data(), &sha1Context);
-    return digest;
-}
-
-std::string CryptoOpenSSL::sha1Final()
-{
-    auto digest = sha1FinalBytes();
-    return std::string(digest.begin(), digest.end());
-}
-
-// HMAC SHA1
-std::vector<uint8_t> CryptoOpenSSL::sha1HMAC(const std::vector<uint8_t>& inputKey, const std::vector<uint8_t>& message)
-{
-    std::vector<uint8_t> digest(20); // 20 is 160 bits
-    auto hmacContext = HMAC_CTX_new();
-    HMAC_Init_ex(hmacContext, inputKey.data(), inputKey.size(), EVP_sha1(), NULL);
-    HMAC_Update(hmacContext, message.data(), message.size());
-
-    unsigned int resLen = 0;
-    HMAC_Final(hmacContext, digest.data(), &resLen);
-
-    HMAC_CTX_free(hmacContext);
-
-    return digest;
-}
-
-// AES CTR
-void CryptoOpenSSL::aesCTRXcrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& iv, uint8_t* buffer, size_t nbytes)
-{
-    // Prepare AES_KEY
-    auto cryptoKey = AES_KEY();
-    AES_set_encrypt_key(key.data(), 128, &cryptoKey);
-
-    // Needed for openssl internal cache
-    unsigned char ecountBuf[16] = {0};
-    unsigned int offsetInBlock = 0;
-
-    CRYPTO_ctr128_encrypt(
-            buffer,
-            buffer,
-        nbytes,
-        &cryptoKey,
-        iv.data(),
-        ecountBuf,
-        &offsetInBlock,
-        block128_f(AES_encrypt));
-}
-
-void CryptoOpenSSL::aesECBdecrypt(const std::vector<uint8_t>& key, std::vector<uint8_t>& data)
-{
-    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
-    EVP_CIPHER_CTX_init(ctx);
-    int len = 0;
-
-    EVP_DecryptInit_ex(ctx, EVP_aes_192_ecb(), NULL, key.data(), NULL);
-    EVP_CIPHER_CTX_set_padding(ctx, 0); // disable padding
-    EVP_DecryptUpdate(ctx, data.data(), &len, data.data(), data.size());
-    EVP_DecryptFinal_ex(ctx, data.data() + len, &len);
-
-    EVP_CIPHER_CTX_free(ctx);
-}
-
-// PBKDF2
-std::vector<uint8_t> CryptoOpenSSL::pbkdf2HmacSha1(const std::vector<uint8_t>& password, const std::vector<uint8_t>& salt, int iterations, int digestSize)
-{
-    std::vector<uint8_t> digest(digestSize);
-
-    // Generate PKDF2 digest
-    PKCS5_PBKDF2_HMAC_SHA1((const char *)password.data(), password.size(),
-                           (const unsigned char *)salt.data(), salt.size(), iterations,
-                           digestSize, digest.data());
-    return digest;
-}
-
-void CryptoOpenSSL::dhInit()
-{
-    // Free old context
-    if (this->dhContext != nullptr)
-    {
-        DH_free(this->dhContext);
-    }
-    this->dhContext = DH_new();
-
-    // Set prime and the generator
-    DH_set0_pqg(this->dhContext, BN_bin2bn(DHPrime, DH_KEY_SIZE, NULL), NULL, BN_bin2bn(DHGenerator, 1, NULL));
-
-    // Generate public and private keys and copy them to vectors
-    DH_generate_key(this->dhContext);
-    BN_bn2bin(DH_get0_pub_key(dhContext), this->publicKey.data());
-}
-
-std::vector<uint8_t> CryptoOpenSSL::dhCalculateShared(const std::vector<uint8_t>& remoteKey)
-{
-    auto sharedKey = std::vector<uint8_t>(DH_KEY_SIZE);
-    // Convert remote key to bignum and compute shared key
-    auto pubKey = BN_bin2bn(&remoteKey[0], DH_KEY_SIZE, NULL);
-    DH_compute_key(sharedKey.data(), pubKey, this->dhContext);
-    BN_free(pubKey);
-    return sharedKey;
-}
-
-// Random stuff
-std::vector<uint8_t> CryptoOpenSSL::generateVectorWithRandomData(size_t length)
-{
-    std::vector<uint8_t> randomVec(length);
-    if(RAND_bytes(randomVec.data(), length) == 0)
-    {
-    }
-    return randomVec;
-}

+ 0 - 8
components/spotify/cspot/bell/src/DecoderGlobals.cpp

@@ -1,8 +0,0 @@
-#include "DecoderGlobals.h"
-
-std::shared_ptr<bell::DecodersInstance> bell::decodersInstance;
-
-void bell::createDecoders()
-{
-    bell::decodersInstance = std::make_shared<bell::DecodersInstance>();
-}

+ 88 - 58
components/spotify/cspot/bell/src/HTTPServer.cpp

@@ -55,14 +55,14 @@ std::vector<std::string> bell::HTTPServer::splitUrl(const std::string &url,
 
 void bell::HTTPServer::registerHandler(RequestType requestType,
                                        const std::string &routeUrl,
-                                       httpHandler handler) {
+                                       httpHandler handler,
+                                       bool readBodyToStr) {
     if (routes.find(routeUrl) == routes.end()) {
         routes.insert({routeUrl, std::vector<HTTPRoute>()});
     }
-    this->routes[routeUrl].push_back(HTTPRoute{
-        .requestType = requestType,
-        .handler = handler,
-    });
+    this->routes[routeUrl].push_back(HTTPRoute{.requestType = requestType,
+                                               .handler = handler,
+                                               .readBodyToStr = readBodyToStr});
 }
 
 void bell::HTTPServer::listen() {
@@ -126,8 +126,8 @@ void bell::HTTPServer::listen() {
              HTTPConnection conn = { .buffer = std::vector<uint8_t>(128),
                                      .httpMethod = "" };
 
-             this->connections.insert({ newFd, conn });
-         }
+             this->connections.insert({newFd, conn});
+        }
 
         /* Service other sockets and update set & max */
         maxfd = sockfd;
@@ -140,8 +140,7 @@ void bell::HTTPServer::listen() {
                 FD_CLR(fd, &activeFdSet);
                 this->connections.erase(
                     it++); // or "it = m.erase(it)" since C++11
-            }
-            else {
+            } else {
                 if (fd != sockfd && FD_ISSET(fd, &readFdSet)) {
                     /* Data arriving on an already-connected socket. */
                     readFromClient(fd);
@@ -156,6 +155,10 @@ void bell::HTTPServer::listen() {
 
 void bell::HTTPServer::readFromClient(int clientFd) {
     HTTPConnection &conn = this->connections[clientFd];
+    if (conn.headersRead) {
+        return;
+    }
+    conn.fd = clientFd;
 
     int nbytes = recv(clientFd, (char*) &conn.buffer[0], conn.buffer.size(), 0);
     if (nbytes < 0) {
@@ -165,49 +168,55 @@ void bell::HTTPServer::readFromClient(int clientFd) {
     } else if (nbytes == 0) {
         this->closeConnection(clientFd);
     } else {
-        conn.currentLine +=
-            std::string(conn.buffer.data(), conn.buffer.data() + nbytes);
+        // append buffer to partialBuffer
+        conn.partialBuffer.insert(conn.partialBuffer.end(), conn.buffer.begin(),
+                                  conn.buffer.begin() + nbytes);
+        auto stringifiedBuffer =
+            std::string(conn.partialBuffer.data(),
+                        conn.partialBuffer.data() + conn.partialBuffer.size());
+
     READBODY:
-        if (!conn.isReadingBody) {
-            while (conn.currentLine.find("\r\n") != std::string::npos) {
-                auto line =
-                    conn.currentLine.substr(0, conn.currentLine.find("\r\n"));
-                conn.currentLine = conn.currentLine.substr(
-                    conn.currentLine.find("\r\n") + 2, conn.currentLine.size());
-                if (line.find("GET ") != std::string::npos ||
-                    line.find("POST ") != std::string::npos ||
-                    line.find("OPTIONS ") != std::string::npos) {
-                    conn.httpMethod = line;
-                }
+        auto readSize = 0;
+
+        while (stringifiedBuffer.find("\r\n") != std::string::npos) {
+            auto line =
+                stringifiedBuffer.substr(0, stringifiedBuffer.find("\r\n"));
+            readSize += stringifiedBuffer.find("\r\n") + 2;
+            stringifiedBuffer = stringifiedBuffer.substr(
+                stringifiedBuffer.find("\r\n") + 2, stringifiedBuffer.size());
+            if (line.find("GET ") != std::string::npos ||
+                line.find("POST ") != std::string::npos ||
+                line.find("OPTIONS ") != std::string::npos) {
+                conn.httpMethod = line;
+            }
 
-                if (line.find("Content-Length: ") != std::string::npos) {
-                    conn.contentLength =
-                        std::stoi(line.substr(16, line.size() - 1));
-                }
-                // detect hostname for captive portal
-                if (line.find("Host: connectivitycheck.gstatic.com") !=
-                    std::string::npos) {
-                    conn.isCaptivePortal = true;
-                    BELL_LOG(info, "http", "Captive portal request detected");
-                }
-                if (line.size() == 0) {
-                    if (conn.contentLength != 0) {
-                        conn.isReadingBody = true;
-                        goto READBODY;
+            if (line.find("Content-Length: ") != std::string::npos) {
+                conn.contentLength =
+                    std::stoi(line.substr(16, line.size() - 1));
+            }
+            // detect hostname for captive portal
+            if (line.find("Host: connectivitycheck.gstatic.com") !=
+                std::string::npos) {
+                conn.isCaptivePortal = true;
+                BELL_LOG(info, "http", "Captive portal request detected");
+            }
+            if (line.size() == 0) {
+                if (conn.contentLength != 0) {
+                    // conn.isReadingBody = true;
+                    // remove readSize bytes from partialBuffer
+                    conn.partialBuffer.erase(conn.partialBuffer.begin(),
+                                             conn.partialBuffer.begin() +
+                                                 readSize);
+                    findAndHandleRoute(conn);
+                    // goto READBODY;
+                } else {
+                    if (!conn.isCaptivePortal) {
+                        findAndHandleRoute(conn);
                     } else {
-                        if (!conn.isCaptivePortal) {
-                            findAndHandleRoute(conn.httpMethod,
-                                               conn.currentLine, clientFd);
-                        } else {
-                            this->redirectCaptivePortal(clientFd);
-                        }
+                        this->redirectCaptivePortal(clientFd);
                     }
                 }
             }
-        } else {
-            if (conn.currentLine.size() >= conn.contentLength) {
-                findAndHandleRoute(conn.httpMethod, conn.currentLine, clientFd);
-            }
         }
     }
 }
@@ -367,8 +376,11 @@ bell::HTTPServer::parseQueryString(const std::string &queryString) {
     return query;
 }
 
-void bell::HTTPServer::findAndHandleRoute(std::string &url, std::string &body,
-                                          int connectionFd) {
+void bell::HTTPServer::findAndHandleRoute(HTTPConnection &conn) {
+    conn.headersRead = true;
+    auto connectionFd = conn.fd;
+    // auto body = conn.partialBuffer;
+    auto url = conn.httpMethod;
     std::map<std::string, std::string> pathParams;
     std::map<std::string, std::string> queryParams;
 
@@ -451,18 +463,36 @@ void bell::HTTPServer::findAndHandleRoute(std::string &url, std::string &body,
             }
 
             if (matches) {
-                if (body.find('&') != std::string::npos) {
-                    queryParams = this->parseQueryString(body);
+                auto reader = std::make_unique<RequestBodyReader>(
+                    conn.contentLength, conn.fd, conn.partialBuffer);
+
+                auto body = std::string();
+                if (route.readBodyToStr) {
+                    body.resize(conn.contentLength);
+                    auto read = 0;
+                    while (read < conn.contentLength) {
+                        auto readBytes = reader->read(
+                            body.data() + read, conn.contentLength - read);
+                        read += readBytes;
+                    }
+                    body.resize(read);
+                    if (body.find('&') != std::string::npos) {
+                        queryParams = this->parseQueryString(body);
+                    }
                 }
 
-                HTTPRequest req = {.urlParams = pathParams,
-                                   .queryParams = queryParams,
-                                   .body = body,
-                                   .url = path,
-                                   .handlerId = 0,
-                                   .connection = connectionFd};
-
-                route.handler(req);
+                std::unique_ptr<HTTPRequest> req =
+                    std::make_unique<HTTPRequest>();
+                req->queryParams = queryParams;
+                req->urlParams = pathParams;
+                req->url = path;
+                req->body = body;
+                req->connection = connectionFd;
+                req->handlerId = 0;
+                req->responseReader = std::move(reader);
+                req->contentLength = conn.contentLength;
+
+                route.handler(std::move(req));
                 return;
             }
         }

+ 7 - 1
components/spotify/cspot/bell/src/HTTPStream.cpp

@@ -8,13 +8,19 @@
 #include <cstring>
 #include <stdlib.h>
 #include <sys/types.h>
+#ifdef _WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include "win32shim.h"
+#else
 #include <sys/socket.h>
 #include <netdb.h>
 #include <netinet/in.h>
 #include <unistd.h>
+#include <netinet/tcp.h>
+#endif
 #include <sstream>
 #include <fstream>
-#include <netinet/tcp.h>
 
 bell::HTTPStream::HTTPStream()
 {

+ 1 - 1
components/spotify/cspot/bell/src/platform/esp/TLSSocket.cpp → components/spotify/cspot/bell/src/TLSSocket.cpp

@@ -1,4 +1,4 @@
-#include "platform/TLSSocket.h"
+#include "TLSSocket.h"
 
 /**
  * Platform TLSSocket implementation for the mbedtls

+ 37 - 0
components/spotify/cspot/bell/src/audio/codec/AACDecoder.cpp

@@ -0,0 +1,37 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#include "AACDecoder.h"
+
+AACDecoder::AACDecoder() {
+	aac = AACInitDecoder();
+	pcmData = (int16_t *)malloc(AAC_MAX_NSAMPS * AAC_MAX_NCHANS * sizeof(int16_t));
+}
+
+AACDecoder::~AACDecoder() {
+	AACFreeDecoder(aac);
+	free(pcmData);
+}
+
+bool AACDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
+	frame.sampRateCore = (int)sampleRate;
+	frame.nChans = channelCount;
+	frame.bitsPerSample = bitDepth;
+	return AACSetRawBlockParams(aac, 0, &frame) == 0;
+}
+
+uint8_t *AACDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
+	if (!inData)
+		return nullptr;
+	int status = AACDecode(
+		aac,
+		static_cast<unsigned char **>(&inData),
+		reinterpret_cast<int *>(&inLen),
+		static_cast<short *>(this->pcmData));
+	AACGetLastFrameInfo(aac, &frame);
+	if (status != ERR_AAC_NONE) {
+		lastErrno = status;
+		return nullptr;
+	}
+	outLen = frame.outputSamps * sizeof(int16_t);
+	return (uint8_t *)pcmData;
+}

+ 37 - 0
components/spotify/cspot/bell/src/audio/codec/ALACDecoder.cpp

@@ -0,0 +1,37 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#include "ALACDecoder.h"
+
+ALACDecoder::ALACDecoder() {
+	// aac = AACInitDecoder();
+	// pcmData = (int16_t *)malloc(AAC_MAX_NSAMPS * AAC_MAX_NCHANS * sizeof(int16_t));
+}
+
+ALACDecoder::~ALACDecoder() {
+	// AACFreeDecoder(aac);
+	// free(pcmData);
+}
+
+bool ALACDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
+	// frame.sampRateCore = (int)sampleRate;
+	// frame.nChans = channelCount;
+	// frame.bitsPerSample = bitDepth;
+	// return AACSetRawBlockParams(aac, 0, &frame) == 0;
+}
+
+uint8_t *ALACDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
+	// if (!inData)
+	// 	return nullptr;
+	// int status = AACDecode(
+	// 	aac,
+	// 	static_cast<unsigned char **>(&inData),
+	// 	reinterpret_cast<int *>(&inLen),
+	// 	static_cast<short *>(this->pcmData));
+	// AACGetLastFrameInfo(aac, &frame);
+	// if (status != ERR_AAC_NONE) {
+	// 	lastErrno = status;
+	// 	return nullptr;
+	// }
+	// outLen = frame.outputSamps * sizeof(int16_t);
+	// return (uint8_t *)pcmData;
+}

+ 71 - 0
components/spotify/cspot/bell/src/audio/codec/AudioCodecs.cpp

@@ -0,0 +1,71 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#include "AudioCodecs.h"
+#include <map>
+
+#ifdef BELL_CODEC_AAC
+#include "AACDecoder.h"
+static std::shared_ptr<AACDecoder> codecAac;
+#endif
+
+#ifdef BELL_CODEC_MP3
+#include "MP3Decoder.h"
+static std::shared_ptr<MP3Decoder> codecMp3;
+#endif
+
+#ifdef BELL_CODEC_VORBIS
+#include "VorbisDecoder.h"
+static std::shared_ptr<VorbisDecoder> codecVorbis;
+#endif
+
+#ifdef BELL_CODEC_OPUS
+#include "OPUSDecoder.h"
+static std::shared_ptr<OPUSDecoder> codecOpus;
+#endif
+
+std::map<AudioCodec, std::shared_ptr<BaseCodec>> customCodecs;
+
+std::shared_ptr<BaseCodec> AudioCodecs::getCodec(AudioCodec type) {
+	if (customCodecs.find(type) != customCodecs.end())
+		return customCodecs[type];
+	switch (type) {
+#ifdef BELL_CODEC_AAC
+		case AudioCodec::AAC:
+			if (codecAac)
+				return codecAac;
+			codecAac = std::make_shared<AACDecoder>();
+			return codecAac;
+#endif
+#ifdef BELL_CODEC_MP3
+		case AudioCodec::MP3:
+			if (codecMp3)
+				return codecMp3;
+			codecMp3 = std::make_shared<MP3Decoder>();
+			return codecMp3;
+#endif
+#ifdef BELL_CODEC_VORBIS
+		case AudioCodec::VORBIS:
+			if (codecVorbis)
+				return codecVorbis;
+			codecVorbis = std::make_shared<VorbisDecoder>();
+			return codecVorbis;
+#endif
+#ifdef BELL_CODEC_OPUS
+		case AudioCodec::OPUS:
+			if (codecOpus)
+				return codecOpus;
+			codecOpus = std::make_shared<OPUSDecoder>();
+			return codecOpus;
+#endif
+		default:
+			return nullptr;
+	}
+}
+
+std::shared_ptr<BaseCodec> AudioCodecs::getCodec(BaseContainer *container) {
+	return getCodec(container->codec);
+}
+
+void AudioCodecs::addCodec(AudioCodec type, const std::shared_ptr<BaseCodec> &codec) {
+	customCodecs[type] = codec;
+}

+ 13 - 0
components/spotify/cspot/bell/src/audio/codec/BaseCodec.cpp

@@ -0,0 +1,13 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-12.
+
+#include "BaseCodec.h"
+
+bool BaseCodec::setup(BaseContainer *container) {
+	return this->setup(container->sampleRate, container->channelCount, container->bitDepth);
+}
+
+uint8_t *BaseCodec::decode(BaseContainer *container, uint32_t &outLen) {
+	uint32_t len;
+	auto *data = container->readSample(len);
+	return decode(data, len, outLen);
+}

+ 8 - 0
components/spotify/cspot/bell/src/audio/codec/DecoderGlobals.cpp

@@ -0,0 +1,8 @@
+#include "DecoderGlobals.h"
+
+bell::DecodersInstance* bell::decodersInstance;
+
+void bell::createDecoders()
+{
+    bell::decodersInstance = new bell::DecodersInstance();
+}

+ 35 - 0
components/spotify/cspot/bell/src/audio/codec/MP3Decoder.cpp

@@ -0,0 +1,35 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#include "MP3Decoder.h"
+
+MP3Decoder::MP3Decoder() {
+	mp3 = MP3InitDecoder();
+	pcmData = (int16_t *)malloc(MAX_NSAMP * MAX_NGRAN * MAX_NCHAN * sizeof(int16_t));
+}
+
+MP3Decoder::~MP3Decoder() {
+	MP3FreeDecoder(mp3);
+	free(pcmData);
+}
+
+bool MP3Decoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
+	return true;
+}
+
+uint8_t *MP3Decoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
+	if (!inData)
+		return nullptr;
+	int status = MP3Decode(
+		mp3,
+		static_cast<unsigned char **>(&inData),
+		reinterpret_cast<int *>(&inLen),
+		static_cast<short *>(this->pcmData),
+		/* useSize */ 0);
+	MP3GetLastFrameInfo(mp3, &frame);
+	if (status != ERR_MP3_NONE) {
+		lastErrno = status;
+		return nullptr;
+	}
+	outLen = frame.outputSamps * sizeof(int16_t);
+	return (uint8_t *)pcmData;
+}

+ 46 - 0
components/spotify/cspot/bell/src/audio/codec/OPUSDecoder.cpp

@@ -0,0 +1,46 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#include "OPUSDecoder.h"
+#include "opus.h"
+
+#define MAX_FRAME_SIZE 6 * 960
+#define MAX_CHANNELS   2
+
+// dummy structure, just to get access to channels
+struct OpusDecoder {
+	int dummy1;
+	int dummy2;
+	int channels;
+};
+
+OPUSDecoder::OPUSDecoder() {
+	opus = nullptr;
+	pcmData = (int16_t *)malloc(MAX_FRAME_SIZE * MAX_CHANNELS * sizeof(int16_t));
+}
+
+OPUSDecoder::~OPUSDecoder() {
+	if (opus)
+		opus_decoder_destroy(opus);
+	free(pcmData);
+}
+
+bool OPUSDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
+	if (opus)
+		opus_decoder_destroy(opus);
+	opus = opus_decoder_create((int32_t)sampleRate, channelCount, &lastErrno);
+	return !lastErrno;
+}
+
+uint8_t *OPUSDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
+	if (!inData)
+		return nullptr;
+	outLen = opus_decode(
+		opus,
+		static_cast<unsigned char *>(inData),
+		static_cast<int32_t>(inLen),
+		pcmData,
+		MAX_FRAME_SIZE,
+		false);
+	outLen *= opus->channels * sizeof(int16_t);
+	return (uint8_t *)pcmData;
+}

+ 120 - 0
components/spotify/cspot/bell/src/audio/codec/VorbisDecoder.cpp

@@ -0,0 +1,120 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-14.
+
+#include "VorbisDecoder.h"
+#include "AudioCodecs.h"
+
+extern "C" {
+extern vorbis_dsp_state *vorbis_dsp_create(vorbis_info *vi);
+extern void vorbis_dsp_destroy(vorbis_dsp_state *v);
+extern int vorbis_dsp_restart(vorbis_dsp_state *v);
+extern int vorbis_dsp_headerin(vorbis_info *vi, vorbis_comment *vc, ogg_packet *op);
+extern int vorbis_dsp_synthesis(vorbis_dsp_state *vd, ogg_packet *op, int decodep);
+extern int vorbis_dsp_pcmout(vorbis_dsp_state *v, ogg_int16_t *pcm, int samples);
+extern int vorbis_dsp_read(vorbis_dsp_state *v, int samples);
+}
+
+#define VORBIS_BUF_SAMPLES	1024
+#define VORBIS_BUF_CHANNELS 2
+
+VorbisDecoder::VorbisDecoder() {
+	vi = new vorbis_info;
+	vorbis_info_init(vi);
+	vc = new vorbis_comment;
+	vorbis_comment_init(vc);
+
+	op.packet = new ogg_reference;
+	op.packet->buffer = new ogg_buffer;
+	op.packet->buffer->refcount = 0;
+	op.packet->buffer->ptr.owner = nullptr;
+	op.packet->buffer->ptr.next = nullptr;
+	op.packet->begin = 0;
+	op.packet->next = nullptr;
+	op.granulepos = -1;
+	op.packetno = 10;
+
+	pcmData = (int16_t *)malloc(VORBIS_BUF_SAMPLES * VORBIS_BUF_CHANNELS * sizeof(uint16_t));
+}
+
+VorbisDecoder::~VorbisDecoder() {
+	vorbis_info_clear(vi);
+	vorbis_comment_clear(vc);
+	if (vd)
+		vorbis_dsp_destroy(vd);
+	vd = nullptr;
+	free(pcmData);
+}
+
+bool VorbisDecoder::setup(BaseContainer *container) {
+	uint32_t setupLen;
+	uint8_t *setup = container->getSetupData(setupLen, AudioCodec::VORBIS);
+	if (!setup)
+		return false;
+	op.b_o_s = true;				   // mark this page as beginning of stream
+	uint32_t bytesLeft = setupLen - 1; // minus header count length (8 bit)
+	std::vector<uint32_t> headers(setup[0]);
+	for (uint8_t i = 0; i < setup[0]; i++) {
+		uint8_t *sizeByte = (uint8_t *)setup + 1 + i;
+		headers[i] = 0;
+		while (*sizeByte == 255) {
+			headers[i] += *(sizeByte++);
+			bytesLeft--;
+		}
+		headers[i] += *sizeByte;
+		bytesLeft--;
+	}
+	// parse all headers from the setup data
+	for (const auto &headerSize : headers) {
+		setPacket(setup + setupLen - bytesLeft, headerSize);
+		bytesLeft -= headerSize;
+		lastErrno = vorbis_dsp_headerin(vi, vc, &op);
+		if (lastErrno < 0) {
+			bytesLeft = 0;
+			break;
+		}
+	}
+	// parse last header, not present in header table (seems to happen for MP4 containers)
+	if (bytesLeft) {
+		setPacket(setup + setupLen - bytesLeft, bytesLeft);
+		lastErrno = vorbis_dsp_headerin(vi, vc, &op);
+	}
+	// disable BOS to allow reading audio data
+	op.b_o_s = false;
+	// set up the codec
+	if (vd)
+		vorbis_dsp_restart(vd);
+	else
+		vd = vorbis_dsp_create(vi);
+	return !lastErrno;
+}
+
+bool VorbisDecoder::setup(uint32_t sampleRate, uint8_t channelCount, uint8_t bitDepth) {
+	// manual setup is not allowed
+	return false;
+}
+
+uint8_t *VorbisDecoder::decode(uint8_t *inData, uint32_t inLen, uint32_t &outLen) {
+	if (!inData || !vi)
+		return nullptr;
+	setPacket(inData, inLen);
+	// sources:
+	//  - vorbisfile.c:556
+	//  - vorbisfile.c:1557
+	lastErrno = vorbis_dsp_synthesis(vd, &op, 1);
+	if (lastErrno < 0)
+		return nullptr;
+	int samples = vorbis_dsp_pcmout(vd, pcmData, VORBIS_BUF_SAMPLES);
+	outLen = samples;
+	if (samples) {
+		if (samples > 0) {
+			vorbis_dsp_read(vd, samples);
+			outLen = samples * 2 * vi->channels;
+		}
+	}
+	return (uint8_t *)pcmData;
+}
+
+void VorbisDecoder::setPacket(uint8_t *inData, uint32_t inLen) const {
+	op.packet->buffer->data = static_cast<unsigned char *>(inData);
+	op.packet->buffer->size = static_cast<long>(inLen);
+	op.packet->length = static_cast<long>(inLen);
+}

+ 18 - 0
components/spotify/cspot/bell/src/audio/container/AudioContainers.cpp

@@ -0,0 +1,18 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-15.
+
+#include "AudioContainers.h"
+#include "Mpeg4Container.h"
+#ifdef _WIN32
+#include "win32shim.h"
+#endif
+
+std::unique_ptr<BaseContainer> AudioContainers::create(const char *mimeType) {
+	char *type = strchr((char *)mimeType, '/');
+	if (!type || *(++type) == '\0')
+		return nullptr;
+
+	if (strncasecmp(type, "mp4", 3) == 0)
+		return std::make_unique<Mpeg4Container>();
+
+	return nullptr;
+}

+ 78 - 0
components/spotify/cspot/bell/src/audio/container/BaseContainer.cpp

@@ -0,0 +1,78 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-7.
+
+#include "BaseContainer.h"
+
+void BaseContainer::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
+	this->reader = std::make_unique<bell::BinaryReader>(stream);
+	this->source = stream;
+	this->pos = position;
+}
+
+// TODO move source stream reading here, and set closed = true when stream ends
+
+uint8_t BaseContainer::readUint8() {
+	pos += 1;
+	return reader->readByte();
+}
+
+uint16_t BaseContainer::readUint16() {
+	pos += 2;
+	return reader->readShort();
+}
+
+uint32_t BaseContainer::readUint24() {
+	uint8_t b[3];
+	readBytes(b, 3);
+	return static_cast<int32_t>((b[2]) | (b[1] << 8) | (b[0] << 16));
+}
+
+uint32_t BaseContainer::readUint32() {
+	pos += 4;
+	return reader->readUInt();
+}
+
+uint64_t BaseContainer::readUint64() {
+	pos += 8;
+	return reader->readLong();
+}
+
+uint32_t BaseContainer::readVarint32() {
+	uint8_t b = readUint8();
+	uint32_t result = b & 0x7f;
+	while (b & 0b10000000) {
+		b = readUint8();
+		result <<= 7;
+		result |= b & 0x7f;
+	}
+	return result;
+}
+
+uint32_t BaseContainer::readBytes(uint8_t *dst, uint32_t num) {
+	if (!num)
+		return 0;
+	uint32_t len, total = 0;
+	do {
+		if (dst) {
+			len = source->read(dst, num);
+			dst += len; // increment destination pointer
+		} else {
+			len = source->skip(num);
+		}
+		total += len; // increment total read count
+		pos += len;	  // increment absolute source position
+		num -= len;	  // decrement bytes left to read
+	} while (len && num);
+	if (!len) // source->read() returned 0, it's closed
+		closed = true;
+	return len;
+}
+
+uint32_t BaseContainer::skipBytes(uint32_t num) {
+	return readBytes(nullptr, num);
+}
+
+uint32_t BaseContainer::skipTo(uint32_t offset) {
+	if (offset <= pos)
+		return 0;
+	return readBytes(nullptr, offset - pos);
+}

+ 359 - 0
components/spotify/cspot/bell/src/audio/container/Mpeg4Container.cpp

@@ -0,0 +1,359 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-8.
+
+#include "Mpeg4Container.h"
+#include "AudioCodecs.h"
+#include "Mpeg4Atoms.h"
+#include "Mpeg4Types.h"
+
+Mpeg4Container::~Mpeg4Container() {
+	freeAll();
+	this->source->close();
+}
+
+void Mpeg4Container::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
+	BaseContainer::feed(stream, position);
+	if (isParsed) {
+		// this is needed to support seeking backwards, as goToData() always moves forward only
+		setCurrentFragment();
+		isInData = false;
+		goToData();
+	}
+}
+
+bool Mpeg4Container::parse() {
+	freeAll();
+	if (pos != 0) {
+		isParsed = false;
+		return false;
+	}
+	uint32_t size;
+	AtomType type;
+	uint32_t moovEnd = 0, mdiaEnd = 0;
+	bool parsed = false, error = false, hasMoov = false, hasHdlr = false;
+	while (!parsed && !error && !closed) {
+		readAtomHeader(size, (uint32_t &)type);
+		switch (type) {
+				/*case AtomType::ATOM_FTYP:
+					readBytes(mediaBrand, 4);
+					mediaBrand[4] = '\0';
+					skipBytes(size - 4);
+					break;*/
+
+			case AtomType::ATOM_MVEX:
+			case AtomType::ATOM_TRAK:
+			case AtomType::ATOM_MINF:
+			case AtomType::ATOM_STBL:
+				// this causes the next iteration to read the next direct child atom
+				continue;
+			case AtomType::ATOM_MOOV:
+				moovEnd = pos + size;
+				hasMoov = true;
+				continue;
+			case AtomType::ATOM_MDIA:
+				mdiaEnd = pos + size;
+				continue;
+
+			case AtomType::ATOM_TREX:
+				readTrex();
+				break;
+			case AtomType::ATOM_TKHD:
+				skipBytes(12);
+				if (audioTrackId == -1) {
+					audioTrackId = (int8_t)readUint32();
+					skipBytes(size - 16);
+				} else {
+					// new track header, but audio track already found
+					skipTo(moovEnd);
+				}
+				break;
+			case AtomType::ATOM_MDHD:
+				skipBytes(12);
+				timescale = readUint32();
+				totalDuration = readUint32();
+				totalDurationPresent = true;
+				durationMs = totalDuration * 1000LL / timescale;
+				if (!sampleRate)
+					sampleRate = timescale;
+				hasHdlr = false;
+				skipBytes(size - 20);
+				break;
+			case AtomType::ATOM_HDLR:
+				if (hasHdlr) {
+					skipBytes(size);
+					continue;
+				}
+				hasHdlr = true;
+				skipBytes(8);
+				if (readUint32() != (uint32_t)AtomType::ATOM_SOUN) {
+					skipTo(mdiaEnd);   // skip the rest of mdia atom
+					audioTrackId = -1; // unset the track ID, so the next tkhd can set it
+				} else {
+					skipBytes(size - 12);
+				}
+				break;
+			case AtomType::ATOM_STSD:
+				readStsd();
+				break;
+			case AtomType::ATOM_STTS:
+				readStts();
+				break;
+			case AtomType::ATOM_STSC:
+				readStsc();
+				break;
+			case AtomType::ATOM_STCO:
+				readStco();
+				break;
+			case AtomType::ATOM_STSZ:
+				readStsz();
+				break;
+			case AtomType::ATOM_SIDX:
+				readSidx(size);
+				break;
+			case AtomType::ATOM_MOOF:
+			case AtomType::ATOM_MDAT:
+				// the track can be accessed randomly if all the tables are set before parsing the first fragment
+				isSeekable = fragmentsLen || (chunksLen && chunkOffsetsLen && samplesLen && sampleSizesLen);
+				if (type == AtomType::ATOM_MOOF) {
+					// this will seek to the start of mdat header
+					error = !parseMoof(size);
+				} else {
+					// pos already points to sample data
+					isInData = true;
+				}
+				parsed = true;
+				break;
+			default:
+				// ignore unknown atoms
+				skipBytes(size);
+				break;
+		}
+	}
+	if (sampleDescLen) {
+		codec = getCodec(sampleDesc);
+	}
+	if (!hasMoov || audioTrackId == -1 || codec == AudioCodec::UNKNOWN) {
+		// this is not a progressive MP4, can't be played
+		// or has no audio tracks
+		// or has an unknown audio codec
+		freeAll();
+		isParsed = false;
+		return false;
+	}
+	if (isInData) {
+		// [pos] points to mdat, create a dummy fragment for it
+		createFragment()->duration = totalDuration;
+	}
+	isParsed = !error && !closed;
+	setCurrentFragment();
+	setCurrentSample();
+	return isParsed;
+}
+
+bool Mpeg4Container::parseMoof(uint32_t moofSize) {
+	freeFragment();
+	uint32_t size;
+	AtomType type;
+	uint32_t moofOffset = pos - 8;
+	uint32_t moofEnd = pos + moofSize;
+	uint32_t trafEnd = 0;
+
+	bool hasFragment = false;
+	Mpeg4Fragment *fragment;
+	for (fragment = fragments; fragment < fragments + fragmentsLen; fragment++) {
+		if (isInFragment(fragment, pos)) {
+			hasFragment = true;
+			break;
+		}
+	}
+
+	while (pos < moofEnd) {
+		readAtomHeader(size, (uint32_t &)type);
+		switch (type) {
+			case AtomType::ATOM_TRAF:
+				trafEnd = pos + size;
+				continue;
+			case AtomType::ATOM_TFHD:
+				readTfhd(trafEnd, moofOffset);
+				break;
+			case AtomType::ATOM_TRUN:
+				readTrun(size, moofOffset);
+				break;
+			default:
+				skipBytes(size);
+				break;
+		}
+	}
+	// this moof is not in the fragments table
+	if (!hasFragment) {
+		fragment = createFragment();
+	}
+	if (!totalDurationPresent) {
+		// total duration was not found or a new fragment was created
+		uint32_t duration = 0;
+		for (Mpeg4SampleRange *sr = samples; sr < samples + samplesLen; sr++) {
+			duration += sr->count * sr->duration;
+		}
+		fragment->duration = duration;
+		totalDuration += duration;
+		durationMs = totalDuration * 1000LL / timescale;
+	}
+	isFragmented = true;
+	return true;
+}
+
+int32_t Mpeg4Container::getLoadingOffset(uint32_t timeMs) {
+	if (!isParsed)
+		return SAMPLE_NOT_LOADED;
+	if (!isSeekable)
+		return SAMPLE_NOT_SEEKABLE;
+	// timeScaled - specified time in the media time coordinate system
+	uint64_t timeScaled = (uint64_t)timeMs * timescale / 1000LL;
+	uint64_t timeAbs = 0;
+
+	Mpeg4Fragment *fragment = fragments;
+	for (; fragment < fragments + fragmentsLen; fragment++) {
+		timeAbs += fragment->duration; // timeAbs holds the fragment end time
+		if (timeScaled < timeAbs) {
+			timeAbs -= fragment->duration; // set timeAbs to fragment start time
+			break;
+		}
+	}
+	if (!fragment)
+		return SAMPLE_NOT_FOUND;
+	if (fragment != curFragment)
+		return (int32_t)fragment->start;
+	// get the position in bytes
+	return (int32_t)findSample((int64_t)timeScaled, -1, timeAbs);
+}
+
+bool Mpeg4Container::goToData() {
+	if (!isParsed || !curFragment)
+		return false;
+	if (isInData)
+		return true;
+	uint32_t size;
+	AtomType type;
+
+	if (pos == curFragment->start || pos >= curFragment->end) {
+		// fragment ended, or a new one just loaded
+		while (pos >= curFragment->end && curFragment < fragments + fragmentsLen - 1) {
+			// skip to the next fragment header
+			curFragment++;
+		} // else, no more **loaded** fragments
+		if (pos < curFragment->start && !skipTo(curFragment->start))
+			return false;
+		// [pos] is either a fragment header, EOF or unknown data
+		readAtomHeader(size, (uint32_t &)type);
+		if (type == AtomType::ATOM_MOOF) {
+			// fragment header found, try to parse it
+			parseMoof(size);
+			// update [curFragment]
+			setCurrentFragment();
+			// read mdat header
+			readAtomHeader(size, (uint32_t &)type);
+		}
+		if (type != AtomType::ATOM_MDAT)
+			return false;
+	} else if (pos >= curChunk.end) {
+		// chunk ended, but still in [curFragment]
+		if (!curChunk.nextStart) // no more chunks but fragment not ended ??
+			return false;
+		if (pos != curChunk.nextStart && !skipTo(curChunk.nextStart))
+			return false;
+	} /* else {
+		 readAtomHeader(size, (uint32_t &)type);
+		 return false;
+	 }*/
+	// update [isInData], [curChunk] and [curSampleSize]
+	setCurrentSample();
+	if (pos < curChunk.start) {
+		// chunk not started yet, probably a multi-track movie
+		if (!skipTo(curChunk.start))
+			return false;
+		// update [isInData], [curChunk] and [curSampleSize]
+		setCurrentSample();
+	}
+	return true;
+}
+
+bool Mpeg4Container::seekTo(uint32_t timeMs) {
+	if (!isParsed || !isSeekable)
+		return false;
+	// try to go to nearest mdat data
+	if (!goToData())
+		return false;
+	uint32_t offset = getLoadingOffset(timeMs);
+	// check if the required [offset] is in the currently loaded fragment
+	//  - if it is, [offset] points to the required sample
+	//  - if it isn't, [offset] points to a moof header
+	if (!isInFragment(curFragment, offset)) {
+		// try to seek to moof header, fail if not possible
+		if (offset != pos && !skipTo(offset))
+			return false;
+		// [pos] points to a moof header
+		isInData = false;
+		// parse the just loaded atom header (pos >= curFragment->end)
+		if (!goToData())
+			return false;
+		// get the actual sample's offset
+		offset = getLoadingOffset(timeMs);
+		// ...or give up if still not loaded
+		if (!isInFragment(curFragment, offset))
+			return false;
+	}
+	if (!isInData) // something is really not ok
+		return false;
+	// [pos] points to mdat data
+	// [offset] points to the required sample
+	if (!skipTo(offset))
+		return false;
+	// update the current chunk range and sample sizes
+	setCurrentSample();
+	return true;
+}
+
+int32_t Mpeg4Container::getCurrentTimeMs() {
+	if (!curFragment || !isParsed)
+		return 0;
+	int64_t time = 0;
+	// get time offset of the current fragment
+	Mpeg4Fragment *f = fragments;
+	while (f != curFragment) {
+		time += (f++)->duration;
+	}
+	time = findSample(-1, (int32_t)pos, time);
+	if (time < 0)
+		return (int32_t)time;
+	return (int32_t)(time * 1000LL / timescale);
+}
+
+uint8_t *Mpeg4Container::readSample(uint32_t &len) {
+	if (!curFragment || !isParsed)
+		return nullptr;
+	if (!sampleData) {
+		allocSampleData();
+	}
+	// go to mdat
+	if (!isInData && !goToData())
+		return nullptr;
+	len = *curSampleSize;
+	len = readBytes(sampleData, len);
+	skipBytes(*curSampleSize - len); // skip the rest if something went wrong
+	if (sampleSizesLen > 1)
+		curSampleSize++;
+	if (pos >= curChunk.end) {
+		// chunk ended, make goToData() read the next one
+		isInData = false;
+	}
+	return sampleData;
+}
+
+uint8_t *Mpeg4Container::getSetupData(uint32_t &len, AudioCodec matchCodec) {
+	for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDescLen; desc++) {
+		if (matchCodec != getCodec(desc))
+			continue;
+		len = desc->dataLength;
+		return desc->data;
+	}
+	return nullptr;
+}

+ 171 - 0
components/spotify/cspot/bell/src/audio/container/Mpeg4Parser.cpp

@@ -0,0 +1,171 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-10.
+
+#include "BellUtils.h"
+#include "Mpeg4Container.h"
+#include "Mpeg4Types.h"
+
+using namespace bell;
+
+/** Populate [chunks] using the Sample-to-chunk Table */
+void Mpeg4Container::readStsc() {
+	skipBytes(4); // skip version and flags
+	chunksLen = readUint32();
+	chunks = (Mpeg4ChunkRange *)malloc(chunksLen * sizeof(Mpeg4ChunkRange));
+	for (uint32_t i = 0; i < chunksLen; i++) {
+		chunks[i].count = readUint32();
+		chunks[i].samples = readUint32();
+		chunks[i].sampleDescriptionId = readUint32();
+		if (i > 0) {
+			chunks[i - 1].count = chunks[i].count - chunks[i - 1].count;
+		}
+	}
+	if (chunkOffsetsLen) {
+		chunks[chunksLen - 1].count = chunkOffsetsLen - chunks[chunksLen - 1].count + 1;
+	}
+}
+
+/** Populate [chunkOffsets] using the Chunk Offset Table */
+void Mpeg4Container::readStco() {
+	skipBytes(4); // skip version and flags
+	chunkOffsetsLen = readUint32();
+	chunkOffsets = (Mpeg4ChunkOffset *)malloc(chunkOffsetsLen * sizeof(Mpeg4ChunkOffset));
+	for (uint32_t i = 0; i < chunkOffsetsLen; i++) {
+		chunkOffsets[i] = readUint32();
+	}
+	if (chunksLen) {
+		chunks[chunksLen - 1].count = chunkOffsetsLen - chunks[chunksLen - 1].count + 1;
+	}
+}
+
+/** Populate [samples] using the Time-to-sample Table */
+void Mpeg4Container::readStts() {
+	skipBytes(4); // skip version and flags
+	samplesLen = readUint32();
+	samples = (Mpeg4SampleRange *)malloc(samplesLen * sizeof(Mpeg4SampleRange));
+	for (uint32_t i = 0; i < samplesLen; i++) {
+		samples[i].count = readUint32();
+		samples[i].duration = readUint32();
+	}
+}
+
+/** Populate [sampleSizes] using the Sample Size Table */
+void Mpeg4Container::readStsz() {
+	skipBytes(4); // skip version and flags
+	uint32_t sampleSize = readUint32();
+	sampleSizesLen = readUint32();
+	if (sampleSize) {
+		sampleSizesLen = 1;
+	}
+	sampleSizes = (Mpeg4SampleSize *)malloc(sampleSizesLen * sizeof(Mpeg4SampleSize));
+	if (sampleSize) {
+		sampleSizes[0] = sampleSize;
+		if (sampleSize > sampleSizeMax)
+			sampleSizeMax = sampleSize;
+		return;
+	}
+	for (uint32_t i = 0; i < sampleSizesLen; i++) {
+		sampleSize = readUint32();
+		if (sampleSize > sampleSizeMax)
+			sampleSizeMax = sampleSize;
+		sampleSizes[i] = sampleSize;
+	}
+	// reallocate sampleData if the max size changes
+	allocSampleData();
+}
+
+/** Populate [sampleDesc] using the Sample Description Table */
+void Mpeg4Container::readStsd() {
+	// Helpful resources:
+	// - STSD atom structure - ISO/IEC 14496-1 (page 277) - seems to cover QT desc ver.0
+	// - ESDS atom structure - ISO/IEC 14496-1 (page 28)
+	freeAndNull((void *&)sampleDesc);
+	skipBytes(4); // skip version and flags
+	sampleDescLen = readUint32();
+	sampleDesc = (SampleDescription *)malloc(sampleDescLen * sizeof(SampleDescription));
+	for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDescLen; desc++) {
+		uint32_t entryEnd = readUint32() - 4 + pos;
+		uint32_t esdsEnd = entryEnd;
+		// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap2/qtff2.html#//apple_ref/doc/uid/TP40000939-CH204-BBCHHGBH
+		// General Structure of a Sample Description
+		desc->format = (AudioSampleFormat)readUint32();
+		desc->mp4aObjectType = MP4AObjectType::UNDEFINED;
+		desc->mp4aProfile = MP4AProfile::UNDEFINED;
+		skipBytes(6); // reserved
+		desc->dataReferenceIndex = readUint16();
+		// https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFChap3/qtff3.html#//apple_ref/doc/uid/TP40000939-CH205-75770
+		// Sound Sample Description (Version 0)
+		uint16_t version = readUint16();
+		skipBytes(6); // skip Revision level(2), Vendor(4)
+		channelCount = readUint16();
+		bitDepth = readUint16();
+		skipBytes(4); // skip Compression ID(2), Packet size(2)
+		sampleRate = readUint16();
+		skipBytes(2); // decimal part of sample rate
+		if (version >= 1) {
+			// Sound Sample Description (Version 1)
+			skipBytes(16); // skip Samples per packet(4), Bytes per packet(4), Bytes per frame(4), Bytes per sample(4)
+		}
+		// read the child atom
+		uint32_t atomSize;
+		AtomType atomType;
+		readAtomHeader(atomSize, (uint32_t &)atomType);
+		if (atomType == AtomType::ATOM_WAVE) {
+			do {
+				readAtomHeader(atomSize, (uint32_t &)atomType);
+				if (atomType == AtomType::ATOM_ESDS) {
+					esdsEnd = pos + atomSize;
+					break;
+				}
+				skipBytes(atomSize);
+			} while (pos < entryEnd);
+			if (pos >= entryEnd) // something went wrong
+				continue;
+		}
+		if (atomType != AtomType::ATOM_ESDS) {
+			desc->dataType = (uint32_t)atomType;
+			desc->dataLength = atomSize;
+			desc->data = (uint8_t *)malloc(desc->dataLength);
+			readBytes(desc->data, desc->dataLength);
+			continue;
+		}
+		// read ESDS
+		skipBytes(4); // skip esds flags
+		while (pos < esdsEnd) {
+			uint8_t tag = readUint8();
+			uint32_t size = readVarint32();
+			uint8_t flags;
+			switch (tag) {
+				case 0x03: // ES_Descriptor
+					skipBytes(2);
+					flags = readUint8();
+					if (flags & 0b10000000)
+						skipBytes(2);
+					if (flags & 0b01000000)
+						skipBytes(readUint8());
+					if (flags & 0b00100000)
+						skipBytes(2);
+					break;
+				case 0x04: // DecoderConfigDescriptor
+					desc->mp4aObjectType = (MP4AObjectType)readUint8();
+					skipBytes(12);
+					break;
+				case 0x05: // DecoderSpecificInfo
+					if (desc->mp4aObjectType == MP4AObjectType::MP4A) {
+						desc->mp4aProfile = (MP4AProfile)(readUint8() >> 3);
+						skipBytes(size - 1);
+					} else {
+						desc->dataType = 0;
+						desc->dataLength = size;
+						desc->data = (uint8_t *)malloc(desc->dataLength);
+						readBytes(desc->data, desc->dataLength);
+					}
+					break;
+				default:
+					skipBytes(size);
+					break;
+			}
+		}
+		// skip leftover atoms for version 1 QuickTime descriptors
+		skipTo(entryEnd);
+	}
+}

+ 162 - 0
components/spotify/cspot/bell/src/audio/container/Mpeg4ParserFrag.cpp

@@ -0,0 +1,162 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-10.
+
+#include "Mpeg4Container.h"
+
+/** Populate [fragments] using the Segment Index Table */
+void Mpeg4Container::readSidx(uint32_t atomSize) {
+	// https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf (page 121)
+	uint32_t offset = pos + atomSize;
+	uint8_t version = readUint8();
+	skipBytes(3);						// skip flags
+	if (audioTrackId != readUint32()) { // check reference_ID
+		skipBytes(atomSize - 8);		// skip the rest
+		return;
+	}
+	skipBytes(4);				 // skip uint(32) timescale
+	skipBytes(version ? 12 : 4); // skip zeroes, depending on version
+	offset += readUint32();		 // read first_offset
+	skipBytes(2);				 // skip uint(16) reserved
+
+	fragmentsLen = readUint16();
+	fragments = (Mpeg4Fragment *)malloc(fragmentsLen * sizeof(Mpeg4Fragment));
+	for (uint32_t i = 0; i < fragmentsLen; i++) {
+		auto size = readUint32();
+		if (size & (1 << 31)) {
+			skipBytes(8);
+			continue; // ignore references to sidx, for now
+		}
+		fragments[i].start = offset;
+		fragments[i].end = offset + size;
+		fragments[i].duration = readUint32();
+		offset += size;
+		skipBytes(4); // skip SAP info
+	}
+	isFragmented = true;
+}
+
+/** Populate [sampleDefs] using Track Extends */
+void Mpeg4Container::readTrex() {
+	// https://b.goeswhere.com/ISO_IEC_14496-12_2015.pdf (page 69)
+	skipBytes(4); // skip version and flags
+	sampleDefsLen++;
+	sampleDefs = (SampleDefaults *)realloc(sampleDefs, sampleDefsLen * sizeof(SampleDefaults));
+	sampleDefTracks = (uint32_t *)realloc(sampleDefTracks, sampleDefsLen * sizeof(uint32_t));
+	uint32_t i = sampleDefsLen - 1;
+	sampleDefTracks[i] = readUint32();
+	sampleDefs[i].offset = 0;
+	sampleDefs[i].sampleDescriptionId = readUint32();
+	sampleDefs[i].duration = readUint32();
+	sampleDefs[i].size = readUint32();
+	sampleDefs[i].flags = readUint32();
+}
+
+/** Populate [sampleDefs] using Track Fragment Header */
+void Mpeg4Container::readTfhd(uint32_t trafEnd, uint32_t moofOffset) {
+	skipBytes(1); // skip version
+	TfFlags tfhdFlags = {};
+	readBytes((uint8_t *)&tfhdFlags, 3);
+	if (audioTrackId != readUint32()) {
+		skipTo(trafEnd); // skip the rest of traf
+		return;
+	}
+	auto *def = getSampleDef(audioTrackId);
+	if (!def) {
+		skipTo(trafEnd); // error?
+		return;
+	}
+	def->offset = 0;
+	if (tfhdFlags.baseDataOffsetPresent)
+		def->offset = readUint64();
+	if (tfhdFlags.sampleDescriptionIndexPresent)
+		def->sampleDescriptionId = readUint32();
+	if (tfhdFlags.defaultSampleDurationPresent)
+		def->duration = readUint32();
+	if (tfhdFlags.defaultSampleSizePresent)
+		def->size = readUint32();
+	if (tfhdFlags.defaultSampleFlagsPresent)
+		def->flags = readUint32();
+	if (tfhdFlags.defaultBaseIsMoof)
+		def->offset += moofOffset;
+}
+
+/** Populate [chunks, chunkOffsets, samples, sampleSizes] using Track Fragment Run Table */
+void Mpeg4Container::readTrun(uint32_t atomSize, uint32_t moofOffset) {
+	skipBytes(1); // skip version
+	TrFlags trunFlags = {};
+	readBytes((uint8_t *)&trunFlags, 3);
+	// audioTrackId is guaranteed to match this trun's track ID
+	auto *def = getSampleDef(audioTrackId);
+	if (!def) {
+		skipBytes(atomSize - 4); // error?
+		return;
+	}
+	uint32_t i, j = 0;
+	uint32_t sampleCnt = readUint32();
+	uint32_t offset = def->offset ? def->offset : moofOffset; // base offset is baseDataOffset or moofOffset
+	// SampleFlags flags = def->flags;
+	if (trunFlags.dataOffsetPresent)
+		offset += readUint32();
+	// if (trunFlags.firstSampleFlagsPresent)
+	// 	flags = readUint32();
+
+	// treat every trun as a single new chunk
+	i = chunksLen++;
+	chunks = (Mpeg4ChunkRange *)realloc(chunks, chunksLen * sizeof(Mpeg4ChunkRange));
+	chunks[i].count = 1;
+	chunks[i].samples = sampleCnt;
+	chunks[i].sampleDescriptionId = def->sampleDescriptionId;
+	i = chunkOffsetsLen++;
+	chunkOffsets = (Mpeg4ChunkOffset *)realloc(chunkOffsets, chunkOffsetsLen * sizeof(Mpeg4ChunkOffset));
+	chunkOffsets[i] = offset;
+
+	// add all samples' sizes from this trun
+	i = sampleSizesLen;
+	sampleSizesLen += sampleCnt;
+	sampleSizes = (Mpeg4SampleSize *)realloc(sampleSizes, sampleSizesLen * sizeof(Mpeg4SampleSize));
+	// count duration changes for Mpeg4SampleRanges
+	auto *durations = (uint32_t *)malloc(sampleCnt * sizeof(uint32_t));
+	uint32_t prevDuration = 0, durationChanges = 0;
+
+	// TODO optimize memory usage for when all samples are of equal sizes
+	for (; i < sampleSizesLen; i++) {
+		durations[j] = trunFlags.sampleDurationPresent ? readUint32() : def->duration;
+		sampleSizes[i] = trunFlags.sampleSizePresent ? readUint32() : def->size;
+		if (sampleSizes[i] > sampleSizeMax)
+			sampleSizeMax = sampleSizes[i];
+		if (trunFlags.sampleFlagsPresent)
+			skipBytes(4); // skip flags, for now
+		if (trunFlags.sampleCompositionTimeOffsetsPresent)
+			skipBytes(4); // skip sample_composition_time_offset
+		// count duration changes
+		if (durations[j] != prevDuration) {
+			prevDuration = durations[j];
+			durationChanges++;
+		}
+		j++;
+	}
+
+	// add each duration change as a sample range
+	i = samplesLen;
+	samplesLen += durationChanges;
+	samples = (Mpeg4SampleRange *)realloc(samples, samplesLen * sizeof(Mpeg4SampleRange));
+	prevDuration = 0;
+	uint32_t durationCnt = 0; // how many consecutive samples have this duration
+	for (j = 0; j < sampleCnt; j++) {
+		if (durations[j] != prevDuration) {
+			if (prevDuration) {
+				samples[i].count = durationCnt;
+				samples[i].duration = prevDuration;
+			}
+			prevDuration = durations[j];
+			durationCnt = 1;
+		} else {
+			durationCnt++;
+		}
+	}
+	samples[samplesLen - 1].count = durationCnt;
+	samples[samplesLen - 1].duration = prevDuration;
+	// free temp array
+	free(durations);
+	// reallocate sampleData if the max size changes
+	allocSampleData();
+}

+ 215 - 0
components/spotify/cspot/bell/src/audio/container/Mpeg4Utils.cpp

@@ -0,0 +1,215 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-10.
+
+#include "AudioCodecs.h"
+#include "BellUtils.h"
+#include "Mpeg4Container.h"
+#include "Mpeg4Types.h"
+
+using namespace bell;
+
+void Mpeg4Container::readAtomHeader(uint32_t &size, uint32_t &type) {
+	size = readUint32() - 8;
+	type = readUint32();
+}
+
+void Mpeg4Container::allocSampleData() {
+	if (sampleSizeMax != sampleDataLen) {
+		freeAndNull((void *&)sampleData);
+		sampleData = (uint8_t *)realloc(sampleData, sampleSizeMax);
+		sampleDataLen = sampleSizeMax;
+	}
+}
+
+void Mpeg4Container::freeAll() {
+	freeAndNull((void *&)fragments);
+	fragmentsLen = 0;
+	freeAndNull((void *&)sampleDefs);
+	freeAndNull((void *&)sampleDefTracks);
+	sampleDefsLen = 0;
+	for (SampleDescription *desc = sampleDesc; desc < sampleDesc + sampleDefsLen; desc++) {
+		free(desc->data);
+	}
+	freeAndNull((void *&)sampleDesc);
+	sampleDescLen = 0;
+	freeAndNull((void *&)sampleData);
+	freeFragment();
+}
+
+void Mpeg4Container::freeFragment() {
+	freeAndNull((void *&)chunks);
+	chunksLen = 0;
+	freeAndNull((void *&)chunkOffsets);
+	chunkOffsetsLen = 0;
+	freeAndNull((void *&)samples);
+	samplesLen = 0;
+	freeAndNull((void *&)sampleSizes);
+	sampleSizesLen = 0;
+}
+
+SampleDefaults *Mpeg4Container::getSampleDef(uint32_t trackId) {
+	for (uint32_t i = 0; i < sampleDefsLen; i++) {
+		if (sampleDefTracks[i] == trackId)
+			return sampleDefs + i;
+	}
+	return nullptr;
+}
+
+bool Mpeg4Container::isInFragment(Mpeg4Fragment *f, uint32_t offset) {
+	return offset >= f->start && offset < f->end;
+}
+
+void Mpeg4Container::setCurrentFragment() {
+	if (!isParsed)
+		return;
+	for (Mpeg4Fragment *f = fragments; f < fragments + fragmentsLen; f++) {
+		if (isInFragment(f, pos)) {
+			curFragment = f;
+			return;
+		}
+	}
+	curFragment = nullptr;
+}
+
+void Mpeg4Container::setCurrentSample() {
+	if (!isParsed)
+		return;
+	Mpeg4ChunkRange *chunk = chunks;
+	Mpeg4ChunkOffset *chunkOffset = chunkOffsets;
+	uint32_t chunkCnt = chunk->count;
+	uint32_t chunkSampleCnt = chunk->samples;
+	curChunk.start = 0;
+	curChunk.end = 0;
+	uint32_t offset = *chunkOffset;
+	Mpeg4SampleSize *ss = sampleSizes;
+	while (ss < sampleSizes + sampleSizesLen) {
+		// for (Mpeg4SampleSize *ss = sampleSizes; ss < sampleSizes + sampleSizesLen; ss++) {
+		offset += *ss;
+		if (!curChunk.start && pos < offset) {				 // sample found
+			curChunk.start = chunkOffset ? *chunkOffset : 0; // set chunk beginning
+			curSampleSize = ss;								 // store reference to current sample
+		}
+		chunkSampleCnt--;	   // decrease remaining samples in chunk
+		if (!chunkSampleCnt) { // no more samples
+			chunkOffset++;	   // get next chunk offset
+			if (chunkOffset >= chunkOffsets + chunkOffsetsLen)
+				chunkOffset = nullptr;
+			if (curChunk.start) {									 // chunk ended and beginning already found
+				curChunk.end = offset;								 // set chunk end
+				curChunk.nextStart = chunkOffset ? *chunkOffset : 0; // set next chunk offset
+				break;
+			}
+			if (chunkOffset)
+				offset = *chunkOffset;
+			chunkCnt--;							 // decrease remaining chunks in range
+			if (!chunkCnt) {					 // no more chunks
+				chunk++;						 // get next chunk range
+				if (chunk >= chunks + chunksLen) // something is not ok
+					return;						 // -> fail
+				chunkCnt = chunk->count;		 // update new chunk count from range
+			}
+			chunkSampleCnt = chunk->samples; // update new sample count from range
+		}
+		if (sampleSizesLen > 1)
+			ss++;
+	}
+	isInData = pos >= curChunk.start && pos < curChunk.end;
+}
+
+Mpeg4Fragment *Mpeg4Container::createFragment() {
+	uint32_t i = fragmentsLen++;
+	fragments = (Mpeg4Fragment *)realloc(fragments, fragmentsLen * sizeof(Mpeg4Fragment));
+	fragments[i].start = pos - 8;
+	uint32_t fragmentEnd = chunkOffsets[chunkOffsetsLen - 1];
+	uint32_t lastRangeSamples = chunks[chunksLen - 1].samples;
+	if (sampleSizesLen == 1)
+		fragmentEnd += *sampleSizes * lastRangeSamples;
+	else {
+		for (uint32_t j = sampleSizesLen - lastRangeSamples; j < sampleSizesLen; j++) {
+			fragmentEnd += sampleSizes[j];
+		}
+	}
+	fragments[i].end = fragmentEnd;
+	fragments[i].duration = 0;
+	totalDurationPresent = false;
+	return fragments + i;
+}
+
+AudioCodec Mpeg4Container::getCodec(SampleDescription *desc) {
+	switch (desc->format) {
+		case AudioSampleFormat::OPUS:
+			return AudioCodec::OPUS;
+		case AudioSampleFormat::FLAC:
+			return AudioCodec::FLAC;
+		case AudioSampleFormat::MP4A:
+			switch (desc->mp4aObjectType) {
+				case MP4AObjectType::AAC_LC:
+					return AudioCodec::AAC;
+				case MP4AObjectType::OPUS:
+					return AudioCodec::OPUS;
+				case MP4AObjectType::VORBIS:
+					return AudioCodec::VORBIS;
+				case MP4AObjectType::MPEG1:
+					return AudioCodec::MP3;
+				case MP4AObjectType::MP4A:
+					switch (desc->mp4aProfile) {
+						case MP4AProfile::AAC_LC:
+							return AudioCodec::AAC;
+						case MP4AProfile::LAYER_3:
+							return AudioCodec::MP3;
+						default:
+							return AudioCodec::UNKNOWN;
+					}
+				default:
+					return AudioCodec::UNKNOWN;
+			}
+		default:
+			return AudioCodec::UNKNOWN;
+	}
+}
+
+int64_t Mpeg4Container::findSample(int64_t byTime, int32_t byPos, uint64_t startTime) {
+	Mpeg4ChunkRange *chunk = chunks;
+	Mpeg4SampleRange *sample = samples;
+	Mpeg4ChunkOffset *chunkOffset = chunkOffsets;
+	Mpeg4SampleSize *sampleSize = sampleSizes;
+	uint32_t chunkCnt = chunk->count;
+	uint32_t chunkSampleCnt = chunk->samples;
+	uint32_t sampleRangeCnt = sample->count;
+
+	uint64_t timeAbs = startTime;
+	uint32_t offsetAbs = *chunkOffset;
+	while (sampleSize < sampleSizes + sampleSizesLen) {
+		if (byTime >= 0 && byTime <= timeAbs) {
+			return offsetAbs;
+		}
+		if (byPos >= 0 && byPos <= offsetAbs) {
+			return (int64_t)timeAbs;
+		}
+		timeAbs += sample->duration;
+		sampleRangeCnt--;
+		if (!sampleRangeCnt) {
+			sample++;
+			if (sample > samples + samplesLen)
+				return SAMPLE_NOT_FOUND;
+			sampleRangeCnt = sample->count;
+		}
+		chunkSampleCnt--;
+		if (!chunkSampleCnt) {
+			chunkCnt--;
+			chunkOffset++;
+			if (chunkOffset > chunkOffsets + chunkOffsetsLen)
+				return SAMPLE_NOT_FOUND;
+			offsetAbs = *chunkOffset;
+			if (!chunkCnt) {
+				chunk++;
+				if (chunk > chunks + chunksLen)
+					return SAMPLE_NOT_FOUND;
+				chunkCnt = chunk->count;
+			}
+			chunkSampleCnt = chunk->samples;
+		} else {
+			offsetAbs += sampleSizesLen > 1 ? *(sampleSize++) : *sampleSize;
+		}
+	}
+	return SAMPLE_NOT_FOUND;
+}

+ 172 - 0
components/spotify/cspot/bell/src/audio/container/WebmContainer.cpp

@@ -0,0 +1,172 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-16.
+
+#include "WebmContainer.h"
+#include "AudioCodecs.h"
+#include "BellUtils.h"
+#include "WebmElements.h"
+
+using namespace bell;
+
+#define BLOCK_LACING_MASK 0b110
+
+WebmContainer::~WebmContainer() {
+	freeAndNull((void *&)docType);
+	freeAndNull((void *&)codecId);
+	freeAndNull((void *&)codecPrivate);
+	freeAndNull((void *&)cues);
+	freeAndNull((void *&)sampleData);
+	freeAndNull((void *&)laceSizes);
+}
+
+void WebmContainer::feed(const std::shared_ptr<bell::ByteStream> &stream, uint32_t position) {
+	BaseContainer::feed(stream, position);
+	freeAndNull((void *&)laceSizes);
+	laceLeft = 0;
+	readOutFrameSize = 0;
+}
+
+bool WebmContainer::parse() {
+	bool segmentFound = false;
+	do {
+		readElem();
+		switch (eid) {
+			case ElementId::EBML:
+				continue;
+			case ElementId::DocType:
+				docType = static_cast<char *>(malloc(esize + 1));
+				docType[esize] = '\0';
+				readBytes((uint8_t *)docType, esize);
+				break;
+			case ElementId::Segment:
+				segmentFound = true;
+				parseSegment(pos); // webm can only have a single segment
+				break;
+			default:
+				skipBytes(esize);
+		}
+	} while (!segmentFound);
+	if (strncmp(codecId, "A_OPUS", 6) == 0)
+		codec = AudioCodec::OPUS;
+	else if (strncmp(codecId, "A_VORBIS", 8) == 0)
+		codec = AudioCodec::VORBIS;
+	else
+		codec = AudioCodec::UNKNOWN;
+	isSeekable = cuesLen;
+	return isParsed && codec != AudioCodec::UNKNOWN;
+}
+
+int32_t WebmContainer::getLoadingOffset(uint32_t timeMs) {
+	if (!isSeekable || !cuesLen)
+		return SAMPLE_NOT_SEEKABLE;
+	auto offset = (int32_t)cues[0].offset;
+	auto reqTime = (uint32_t)((float)timeMs * 1000000.0f / timescale);
+	for (CuePoint *cue = cues + 1; cue < cues + cuesLen; cue++) {
+		if (reqTime <= cue->time)
+			return offset;
+		offset = (int32_t)cue->offset;
+	}
+	return offset;
+}
+
+bool WebmContainer::seekTo(uint32_t timeMs) {
+	auto reqTime = (uint32_t)((float)timeMs * 1000000.0f / timescale);
+	if (reqTime <= currentTime)
+		return false;
+	// seeking ¯\_(ツ)_/¯
+	readOutFrameSize = readCluster(reqTime);
+	return !closed;
+}
+
+int32_t WebmContainer::getCurrentTimeMs() {
+	return (int32_t)((float)currentTime * timescale / 1000000.0f);
+}
+
+uint8_t *WebmContainer::readSample(uint32_t &len) {
+	if (readOutFrameSize)
+		return readFrame(readOutFrameSize, len);
+	if (laceLeft && laceLeft--)
+		return readFrame(*(laceCurrent++), len);
+	return readFrame(readCluster(), len);
+}
+
+uint32_t WebmContainer::readCluster(uint32_t untilTime) {
+	uint32_t end;
+	do {
+		readElem();
+		end = pos + esize;
+		switch (eid) {
+			case ElementId::Cluster:
+				continue;
+			case ElementId::Timestamp:
+				clusterTime = readUint(esize);
+				break;
+			case ElementId::BlockGroup:
+				continue;
+			case ElementId::Block:
+			case ElementId::SimpleBlock:
+				if (readVarNum32() != audioTrackId) {
+					skipTo(end);
+					continue;
+				}
+				currentTime = clusterTime + readUint16();
+				if (!untilTime || currentTime >= untilTime)
+					return readBlock(end);
+				skipTo(end); // skip all unneeded frames
+				break;
+			default:
+				skipBytes(esize);
+		}
+	} while (!closed);
+	return 0;
+}
+
+uint32_t WebmContainer::readBlock(uint32_t end) {
+	uint8_t lacing = readUint8() & BLOCK_LACING_MASK;
+	// https://www.matroska.org/technical/basics.html#simpleblock-structure
+	if (!lacing) // no lacing (0b000)
+		return end - pos;
+	// use lacing
+	laceLeft = readUint8() + 1;
+	freeAndNull((void *&)laceSizes);
+	laceSizes = static_cast<uint32_t *>(malloc(laceLeft * sizeof(uint32_t)));
+	auto *size = laceSizes;
+
+	for (uint8_t i = 0; i < laceLeft; i++) {
+		if (lacing == 0b010) { // Xiph lacing (0b010)
+			uint8_t sizeByte = readUint8();
+			*size = sizeByte;
+			while (sizeByte == 255) {
+				sizeByte = readUint8();
+				*size += sizeByte;
+			}
+		} else if (lacing == 0b110) { // EBML lacing (0b110)
+			*size = readVarNum32();
+		} else { // fixed-size lacing (0b100)
+			*size = (end - pos) / laceLeft;
+		}
+		size++;
+	}
+	laceCurrent = laceSizes + 1;
+	laceLeft--;
+	return laceSizes[0];
+}
+
+uint8_t *WebmContainer::readFrame(uint32_t size, uint32_t &outLen) {
+	if (!size)
+		return nullptr;
+	if (size > sampleLen) {
+		free(sampleData);
+		sampleData = static_cast<uint8_t *>(malloc(size));
+		sampleLen = size;
+	}
+	outLen = readBytes(sampleData, size);
+	readOutFrameSize = 0;
+	return sampleData;
+}
+
+uint8_t *WebmContainer::getSetupData(uint32_t &len, AudioCodec matchCodec) {
+	if (codec != matchCodec)
+		return nullptr;
+	len = codecPrivateLen;
+	return codecPrivate;
+}

+ 126 - 0
components/spotify/cspot/bell/src/audio/container/WebmParser.cpp

@@ -0,0 +1,126 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-16.
+
+#include "WebmContainer.h"
+#include "WebmElements.h"
+
+void WebmContainer::parseSegment(uint32_t start) {
+	uint16_t cueIdx = 0;
+	do {
+		readElem();
+		switch (eid) {
+			case ElementId::Info:
+			case ElementId::Tracks:
+				continue;
+			case ElementId::TimestampScale:
+				timescale = (float)readUint(esize);
+				break;
+			case ElementId::Duration:
+				durationMs = (uint32_t)(readFloat(esize) * timescale / 1000000.0f);
+				break;
+			case ElementId::TrackEntry:
+				if (audioTrackId == 255)
+					parseTrack(pos + esize);
+				else // skip other tracks if audio is already found
+					skipBytes(esize);
+				break;
+			case ElementId::Cues:
+				// try to guess the amount of CuePoints from the total size:
+				// - CuePoint = id(1) + size(1) + CueTime + CueTrackPositions
+				// - CueTime = id(1) + size(1) + value(2) # avg. 16 bit
+				// - CueTrackPositions = id(1) + size(1) + CueTrack + CueClusterPosition
+				// - CueTrack = id(1) + size(1) + value(1)
+				// - CueClusterPosition = id(1) + size(1) + value(3) # avg. 24 bit
+				// total: approx. 16 bytes
+				cuesLen += esize / 16;
+				cues = static_cast<CuePoint *>(realloc(cues, cuesLen * sizeof(CuePoint)));
+				continue; // read the next child
+			case ElementId::CuePoint:
+				if (cueIdx >= cuesLen) {
+					cuesLen++;
+					cues = static_cast<CuePoint *>(realloc(cues, cuesLen * sizeof(CuePoint)));
+				}
+				parseCuePoint(cueIdx++, pos + esize, start);
+				break;
+			case ElementId::Cluster:
+				isParsed = audioTrackId != 255;
+				clusterEnd = pos + esize;
+				return;
+			default:
+				skipBytes(esize);
+		}
+	} while (!isParsed);
+}
+
+void WebmContainer::parseTrack(uint32_t end) {
+	uint8_t trackId = 255;
+	uint32_t trackRate = 0;
+	uint8_t trackChannels = 0;
+	uint8_t trackBits = 0;
+	char *trackCodecId = nullptr;
+	uint8_t *trackCodecPrivate = nullptr;
+	uint32_t trackCodecPrivateLen = 0;
+	do {
+		readElem();
+		switch (eid) {
+			case ElementId::TrackNumber:
+				trackId = readUint(esize);
+				break;
+			case ElementId::TrackType:
+				if (readUint8() != 0x02) { // allow only audio tracks
+					skipTo(end);
+					return;
+				}
+				break;
+			case ElementId::CodecID:
+				trackCodecId = static_cast<char *>(malloc(esize + 1));
+				trackCodecId[esize] = '\0';
+				readBytes((uint8_t *)trackCodecId, esize);
+				break;
+			case ElementId::CodecPrivate:
+				trackCodecPrivate = static_cast<uint8_t *>(malloc(esize));
+				trackCodecPrivateLen = esize;
+				readBytes(trackCodecPrivate, esize);
+				break;
+			case ElementId::Audio:
+				continue;
+			case ElementId::SamplingFrequency:
+				trackRate = (uint32_t)readFloat(esize);
+				break;
+			case ElementId::Channels:
+				trackChannels = readUint(esize);
+				break;
+			case ElementId::BitDepth:
+				trackBits = readUint(esize);
+				break;
+			default:
+				skipBytes(esize);
+		}
+	} while (pos < end);
+	// not-audio tracks do not even get to this point
+	audioTrackId = trackId;
+	sampleRate = trackRate;
+	channelCount = trackChannels;
+	bitDepth = trackBits;
+	codecId = trackCodecId;
+	codecPrivate = trackCodecPrivate;
+	codecPrivateLen = trackCodecPrivateLen;
+}
+
+void WebmContainer::parseCuePoint(uint16_t idx, uint32_t end, uint32_t segmentStart) {
+	CuePoint *cue = cues + idx;
+	do {
+		readElem();
+		switch (eid) {
+			case ElementId::CueTime:
+				cue->time = readUint(esize);
+				break;
+			case ElementId::CueTrackPositions:
+				continue;
+			case ElementId::CueClusterPosition:
+				cue->offset = segmentStart + readUint(esize);
+				break;
+			default:
+				skipBytes(esize);
+		}
+	} while (pos < end);
+}

+ 67 - 0
components/spotify/cspot/bell/src/audio/container/WebmUtils.cpp

@@ -0,0 +1,67 @@
+// Copyright (c) Kuba Szczodrzyński 2022-1-16.
+
+#include "WebmContainer.h"
+
+uint32_t WebmContainer::readVarNum32(bool raw) {
+	uint32_t result = readUint8();
+	if (!result) {
+		closed = true;
+		return 0;
+	}
+	uint8_t len = 0;
+	for (; !(result >> (7 - len)); len++) {}
+	if (!raw)
+		result &= ~(1 << (7 - len));
+	for (uint8_t i = 0; i < len; i++) {
+		result <<= 8;
+		result |= readUint8();
+	}
+	return result;
+}
+
+uint64_t WebmContainer::readVarNum64() {
+	uint64_t result = readUint8();
+	if (!result) {
+		closed = true;
+		return 0;
+	}
+	uint8_t len = 0;
+	for (; !(result >> (7 - len)); len++) {}
+	result &= ~(1 << (7 - len));
+	for (uint8_t i = 0; i < len; i++) {
+		result <<= 8;
+		result |= readUint8();
+	}
+	return result;
+}
+
+uint32_t WebmContainer::readUint(uint8_t len) {
+	if (len >= 4) {
+		skipBytes(len - 4);
+		return readUint32();
+	}
+	if (len == 3)
+		return readUint24();
+	if (len == 2)
+		return readUint16();
+	return readUint8();
+}
+
+uint64_t WebmContainer::readUlong(uint8_t len) {
+	if (len == 8)
+		return readUint64();
+	return readUint(len);
+}
+
+float WebmContainer::readFloat(uint8_t len) {
+	double result = 0;
+	auto *b = (uint8_t *)&result;
+	for (uint8_t i = 0; i < len; i++)
+		b[len - i - 1] = readUint8();
+	return (float)result;
+}
+
+void WebmContainer::readElem() {
+	eid = (ElementId)readVarNum32(true);
+	esize = readVarNum32();
+}

部分文件因为文件数量过多而无法显示