update cspot

This commit is contained in:
philippe44
2022-11-17 14:06:00 -08:00
parent a81d0e0513
commit 7e5f27af12
137 changed files with 6046 additions and 836 deletions

View File

@@ -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)

View File

@@ -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),

View File

@@ -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 &);
};

View File

@@ -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})

Binary file not shown.

126
components/spotify/cspot/bell/.gitignore vendored Normal file
View File

@@ -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/

View File

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

View File

@@ -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})
if(BELL_DISABLE_CJSON)
list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/JSONObject.cpp")
else()
list(APPEND EXTRA_INCLUDES "cJSON")
list(APPEND SOURCES "cJSON/cJSON.c")
endif()
if(BELL_EXTERNAL_TREMOR)
list(APPEND EXTRA_LIBS ${BELL_EXTERNAL_TREMOR})
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()

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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};
};

View File

@@ -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;
}
};

View File

@@ -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>

View File

@@ -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);
};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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);

View File

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

View File

@@ -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) {

View File

@@ -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:

View File

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

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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);
};

View File

@@ -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;
};

View File

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

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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();
};

View File

@@ -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
};

View File

@@ -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);
};

View File

@@ -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,
};

View File

@@ -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

View File

@@ -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

View File

@@ -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__

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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); }

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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)

View File

@@ -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()

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -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))

View File

@@ -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;

View File

@@ -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);

Binary file not shown.

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}

View File

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

View File

@@ -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);
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;
}
// 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());
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;
READBODY:
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;
// 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};
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(req);
route.handler(std::move(req));
return;
}
}

View File

@@ -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()
{

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();
}

Some files were not shown because too many files have changed in this diff Show More