Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .github/workflows/microflac-backend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: micro-flac backend

# Builds CHDR_FLAC_BACKEND=microflac and diffs its decoded output against the
# dr_flac backend byte-for-byte, in both settings of CHDR_CD_SCRATCH_BUFFER.
#
# This exists because the backend is opt-in, C++, and fetched at build time
# from a pinned commit rather than vendored - so nothing else in this repo
# compiles it, and it would rot silently. It also compiles against micro-flac's
# internals, which is why the fetch pins a SHA: an upstream change to its
# decode() contract has to fail here rather than in someone's firmware.
#
# The comparison is the point, not the build. micro-flac synthesises a smaller
# STREAMINFO block-size field than the dr_flac backend does, and unlike dr_flac
# it rejects any frame larger than that field - so this is the check that the
# narrower value still accepts everything chdman writes.
#
# It covers two of the three geometries that reach the FLAC decoder:
# cd_cdfl.chd (CD, 2 channels) and hd_flac.chd (the raw flac codec, 2 channels,
# hunk size not a multiple of the block so the last frame is short). AVHuff's
# audio streams also go through this decoder and are NOT covered here - that
# corpus is fetched, not generated, so wiring it in is its own change.

on:
push:
branches: [master]
paths-ignore:
- 'docs/**'
- '**.md'
- 'LICENSE'
- '.gitignore'
- 'contrib/**'
pull_request:
paths-ignore:
- 'docs/**'
- '**.md'
- 'LICENSE'
- '.gitignore'
- 'contrib/**'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}

env:
# micro-flac commit the backend is compiled against. Bump deliberately.
MICROFLAC_SHA: ffbe8a9ba5e78c16e535a4695a8b2418b0c091ee

jobs:
microflac-backend:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v7

- name: Install chdman
run: sudo apt-get update -qq && sudo apt-get install -y --no-install-recommends mame-tools

- name: Generate corpus seeds
run: tests/corpus/generate.sh

- name: Build both backends, both scratch settings
run: |
set -euo pipefail
for s in 0 1; do
cmake -B "build-drflac-$s" -DCMAKE_BUILD_TYPE=Release \
-DCHDR_CD_SCRATCH_BUFFER=$s
cmake --build "build-drflac-$s" --target chd_dump_order -j$(nproc)

cmake -B "build-microflac-$s" -DCMAKE_BUILD_TYPE=Release \
-DCHDR_CD_SCRATCH_BUFFER=$s \
-DCHDR_FLAC_BACKEND=microflac \
-DCHDR_MICROFLAC_GIT_TAG="$MICROFLAC_SHA"
cmake --build "build-microflac-$s" --target chd_dump_order -j$(nproc)
done

- name: Byte-identical decode against dr_flac
run: |
set -euo pipefail
fail=0
total=0
for s in 0 1; do
D="build-drflac-$s/tests/chd_dump_order"
M="build-microflac-$s/tests/chd_dump_order"
for f in tests/corpus/seeds/*.chd; do
for order in sequential reverse random:42; do
total=$((total+1))
"$D" "$f" "$order" > /tmp/d.bin
"$M" "$f" "$order" > /tmp/m.bin
if ! cmp -s /tmp/d.bin /tmp/m.bin; then
echo "MISMATCH: $(basename "$f") $order CHDR_CD_SCRATCH_BUFFER=$s"
fail=$((fail+1))
fi
done
done
done
echo "$total checks, $fail mismatches"
[ "$fail" -eq 0 ]

- name: A default build still needs no C++ compiler
run: |
set -euo pipefail
# enable_language(CXX) must stay inside the microflac branch, or the
# opt-in backend stops being opt-in for anyone without a C++ toolchain.
cmake -B build-nocxx -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_COMPILER=/nonexistent-cxx
cmake --build build-nocxx --target chd_dump_order -j$(nproc)
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ contrib/esp32p4/idf-benchmark/main/embed/
contrib/esp32p4/idf-benchmark/main/embed_list.inc
contrib/esp32p4/idf-benchmark/main/embed_includes.inc
contrib/esp32p4/idf-benchmark/build/
contrib/esp32p4/idf-benchmark/build-*/
contrib/esp32p4/idf-benchmark/sdkconfig
contrib/esp32p4/idf-benchmark/sdkconfig.old
84 changes: 82 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ option(CHDR_WANT_TESTS "Build tests for the library" ON)
# linearly, and never read.
set(CHDR_CD_SCRATCH_BUFFER "" CACHE STRING "Decode CD sectors via a private scratch buffer (ON/OFF; default follows CHDR_LOWRAM_TARGET)")

# FLAC backend. dr_flac is the default and needs nothing extra. microflac is
# faster on MCUs (measured 1.46x on ESP32-S3, 1.41x on ESP32-P4, bit-identical
# output) and smaller, but it is C++ and Apache-2.0, so it is fetched at build
# time rather than vendored - libchdr's own tree stays BSD-3 and a default
# build never touches the network. Point CHDR_MICROFLAC_SOURCE_DIR at a local
# checkout to build offline.
set(CHDR_FLAC_BACKEND "drflac" CACHE STRING "FLAC decoder backend: drflac or microflac")
set_property(CACHE CHDR_FLAC_BACKEND PROPERTY STRINGS drflac microflac)
set(CHDR_MICROFLAC_SOURCE_DIR "" CACHE PATH "Local micro-flac checkout (skips the fetch)")
set(CHDR_MICROFLAC_GIT_TAG "" CACHE STRING "micro-flac commit to fetch (40-char SHA)")

option(BUILD_LTO "Compile libchdr with link-time optimization if supported" OFF)
if(BUILD_LTO)
include(CheckIPOSupported)
Expand Down Expand Up @@ -156,6 +167,52 @@ endif()
# chdr
#--------------------------------------------------

if(CHDR_FLAC_BACKEND STREQUAL "microflac")
enable_language(CXX)
set(CHDR_FLAC_BACKEND_SOURCE src/libchdr_flac_microflac.cpp)
if(CHDR_MICROFLAC_SOURCE_DIR)
set(_microflac_dir "${CHDR_MICROFLAC_SOURCE_DIR}")
else()
# Length and character class separately: CMake's regex engine has no {n}
# repetition, so "[0-9a-f]{40}" silently matches nothing and would reject
# every valid SHA.
string(LENGTH "${CHDR_MICROFLAC_GIT_TAG}" _chdr_microflac_tag_len)
if(CHDR_MICROFLAC_GIT_TAG AND (NOT _chdr_microflac_tag_len EQUAL 40
OR NOT CHDR_MICROFLAC_GIT_TAG MATCHES "^[0-9a-fA-F]+$"))
message(FATAL_ERROR
"CHDR_MICROFLAC_GIT_TAG must be a 40-character commit SHA, got: "
"${CHDR_MICROFLAC_GIT_TAG}")
endif()
if(NOT CHDR_MICROFLAC_GIT_TAG)
message(FATAL_ERROR
"CHDR_FLAC_BACKEND=microflac needs either CHDR_MICROFLAC_SOURCE_DIR (a local "
"checkout) or CHDR_MICROFLAC_GIT_TAG (a 40-character commit SHA). Branch names "
"are deliberately not accepted: the backend is compiled against micro-flac's "
"internals, so the build must be reproducible.")
endif()
include(FetchContent)
FetchContent_Declare(microflac
GIT_REPOSITORY https://github.com/esphome-libs/micro-flac.git
GIT_TAG ${CHDR_MICROFLAC_GIT_TAG})
FetchContent_MakeAvailable(microflac)
set(_microflac_dir "${microflac_SOURCE_DIR}")
endif()
set(CHDR_MICROFLAC_SOURCES
${_microflac_dir}/src/flac_decoder.cpp
${_microflac_dir}/src/decorrelation.cpp
${_microflac_dir}/src/frame_header.cpp
${_microflac_dir}/src/pcm_packing.cpp
${_microflac_dir}/src/crc.cpp
${_microflac_dir}/src/lpc.cpp)
list(APPEND CHDR_DEFINES CHDR_FLAC_BACKEND_MICROFLAC MICRO_FLAC_DISABLE_OGG)
elseif(CHDR_FLAC_BACKEND STREQUAL "drflac")
set(CHDR_FLAC_BACKEND_SOURCE src/libchdr_flac.c)
set(CHDR_MICROFLAC_SOURCES "")
set(_microflac_dir "")
else()
message(FATAL_ERROR "CHDR_FLAC_BACKEND must be drflac or microflac, got: ${CHDR_FLAC_BACKEND}")
endif()

set(CHDR_SOURCES
src/libchdr_bitstream.c
src/libchdr_cdrom.c
Expand All @@ -170,12 +227,34 @@ set(CHDR_SOURCES
src/libchdr_codec_lzma.c
src/libchdr_codec_zlib.c
src/libchdr_codec_zstd.c
src/libchdr_flac.c
${CHDR_FLAC_BACKEND_SOURCE}
src/libchdr_huffman.c
${CHDR_MICROFLAC_SOURCES}
)


# Applies the micro-flac include paths and the C++ settings that keep its
# runtime footprint down. -fno-exceptions/-fno-rtti are what let a bare-metal
# link avoid pulling in libstdc++'s exception and RTTI machinery, and
# -fno-threadsafe-statics drops the __cxa_guard_* dependency.
function(chdr_apply_microflac target)
if(NOT CHDR_FLAC_BACKEND STREQUAL "microflac")
return()
endif()
# Public include dir only: micro-flac's src/ contains a crc.h that will
# shadow a consumer's own header of that name, and its sources reach their
# private headers relative to themselves anyway.
target_include_directories(${target} PRIVATE
${_microflac_dir}/include)
target_compile_options(${target} PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>)
endfunction()

add_library(chdr-static STATIC ${CHDR_SOURCES})
target_include_directories(chdr-static INTERFACE include)
chdr_apply_microflac(chdr-static)
target_link_libraries(chdr-static PRIVATE ${CHDR_LIBS} ${PLATFORM_LIBS})
# libchdr_codec_zlib.c calls tinfl_decompress() directly, so it must be renamed
# alongside miniz's definition - otherwise on ESP-IDF this call binds to the
Expand Down Expand Up @@ -207,6 +286,7 @@ endif()
if (BUILD_SHARED_LIBS)
add_library(chdr SHARED ${CHDR_SOURCES})
target_include_directories(chdr INTERFACE include)
chdr_apply_microflac(chdr)
target_link_libraries(chdr PRIVATE ${CHDR_LIBS} ${PLATFORM_LIBS})
if(CHDR_NEEDS_MINIZ_RENAME)
libchdr_apply_esp_rom_miniz_workaround(chdr)
Expand All @@ -232,7 +312,7 @@ if (BUILD_SHARED_LIBS)

set_target_properties(chdr PROPERTIES C_VISIBILITY_PRESET hidden)
set_target_properties(chdr PROPERTIES VISIBILITY_INLINES_HIDDEN 1)
set_target_properties(chdr PROPERTIES PUBLIC_HEADER "include/libchdr/bitstream.h;include/libchdr/cdrom.h;include/libchdr/chd.h;include/libchdr/chdconfig.h;include/libchdr/coretypes.h;include/libchdr/flac.h;include/libchdr/huffman.h;include/libchdr/macros.h")
set_target_properties(chdr PROPERTIES PUBLIC_HEADER "include/libchdr/bitstream.h;include/libchdr/cdrom.h;include/libchdr/chd.h;include/libchdr/chdconfig.h;include/libchdr/coretypes.h;include/libchdr/huffman.h;include/libchdr/macros.h")
set_target_properties(chdr PROPERTIES VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" SOVERSION ${PROJECT_VERSION_MAJOR})

if (CMAKE_BUILD_TYPE MATCHES Release)
Expand Down
53 changes: 52 additions & 1 deletion contrib/esp32p4/idf-benchmark/components/libchdr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,42 @@ endif()

set(LIBCHDR_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../../..")

# FLAC backend, mirroring libchdr's own CHDR_FLAC_BACKEND. dr_flac is the
# default. microflac is measurably faster here - 1.46x on the S3 and 1.41x on
# the P4 decoding CD audio - but it is C++ and Apache-2.0, so it is not
# vendored: point CHDR_MICROFLAC_SOURCE_DIR at a checkout, or add
# esphome/micro-flac to the project's idf_component.yml and point this at
# ${CMAKE_BINARY_DIR}/../managed_components/esphome__micro-flac.
if(NOT DEFINED CHDR_FLAC_BACKEND)
set(CHDR_FLAC_BACKEND drflac)
endif()
if(CHDR_FLAC_BACKEND STREQUAL "microflac")
if(NOT CHDR_MICROFLAC_SOURCE_DIR)
message(FATAL_ERROR "CHDR_FLAC_BACKEND=microflac needs CHDR_MICROFLAC_SOURCE_DIR")
endif()
# micro-flac enables its Xtensa LPC assembly whenever the core has the
# required features, so the .S files must be compiled in on esp32/esp32s3
# or the link fails on restore_lpc_*_asm. Worth ~1.064x on its own; the
# RISC-V parts take the C path and need nothing extra.
set(CHDR_MICROFLAC_ASM "")
if(IDF_TARGET STREQUAL "esp32" OR IDF_TARGET STREQUAL "esp32s3")
set(CHDR_MICROFLAC_ASM
"${CHDR_MICROFLAC_SOURCE_DIR}/src/xtensa/lpc_32_xtensa.S"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/xtensa/lpc_64_xtensa.S")
endif()
set(CHDR_FLAC_SRCS
"${LIBCHDR_ROOT}/src/libchdr_flac_microflac.cpp"
${CHDR_MICROFLAC_ASM}
"${CHDR_MICROFLAC_SOURCE_DIR}/src/flac_decoder.cpp"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/decorrelation.cpp"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/frame_header.cpp"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/pcm_packing.cpp"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/crc.cpp"
"${CHDR_MICROFLAC_SOURCE_DIR}/src/lpc.cpp")
else()
set(CHDR_FLAC_SRCS "${LIBCHDR_ROOT}/src/libchdr_flac.c")
endif()

idf_component_register(
SRCS
"${LIBCHDR_ROOT}/src/libchdr_bitstream.c"
Expand All @@ -35,7 +71,7 @@ idf_component_register(
"${LIBCHDR_ROOT}/src/libchdr_codec_lzma.c"
"${LIBCHDR_ROOT}/src/libchdr_codec_zlib.c"
"${LIBCHDR_ROOT}/src/libchdr_codec_zstd.c"
"${LIBCHDR_ROOT}/src/libchdr_flac.c"
${CHDR_FLAC_SRCS}
"${LIBCHDR_ROOT}/src/libchdr_huffman.c"
"${LIBCHDR_ROOT}/deps/lzma-26.02/src/LzmaDec.c"
"${LIBCHDR_ROOT}/deps/miniz-3.1.2/miniz.c"
Expand All @@ -44,6 +80,21 @@ idf_component_register(
"${LIBCHDR_ROOT}/include"
)

if(CHDR_FLAC_BACKEND STREQUAL "microflac")
# Only micro-flac's public include dir: its src/ holds a crc.h that will
# shadow a consumer's own header of that name. Its sources reach their
# private headers relative to themselves.
target_include_directories(${COMPONENT_LIB} PRIVATE
"${CHDR_MICROFLAC_SOURCE_DIR}/include")
target_compile_definitions(${COMPONENT_LIB} PRIVATE
CHDR_FLAC_BACKEND_MICROFLAC MICRO_FLAC_DISABLE_OGG)
# Keeps libstdc++'s exception, RTTI and guard machinery out of the image.
target_compile_options(${COMPONENT_LIB} PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>)
endif()

target_compile_definitions(${COMPONENT_LIB} PRIVATE
WANT_RAW_DATA_SECTOR=1
WANT_SUBCODE=1
Expand Down
39 changes: 38 additions & 1 deletion contrib/rp2350/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,32 @@ endif()
# (+15,980 bytes per LZMA instance at CHD's lc=3/lp=0). Off by default.
option(CHDR_LZMA_PROB32 "Use 32-bit LZMA probability model" OFF)

# FLAC backend, mirroring libchdr's own CHDR_FLAC_BACKEND option. dr_flac is
# the default; microflac needs CHDR_MICROFLAC_SOURCE_DIR pointing at a checkout.
if(NOT DEFINED CHDR_FLAC_BACKEND)
set(CHDR_FLAC_BACKEND drflac)
endif()
if(CHDR_FLAC_BACKEND STREQUAL "microflac")
enable_language(CXX)
if(NOT CHDR_MICROFLAC_SOURCE_DIR)
message(FATAL_ERROR "CHDR_FLAC_BACKEND=microflac needs CHDR_MICROFLAC_SOURCE_DIR")
endif()
set(CHDR_FLAC_SRC
${LIBCHDR_ROOT}/src/libchdr_flac_microflac.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/flac_decoder.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/decorrelation.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/frame_header.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/pcm_packing.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/crc.cpp
${CHDR_MICROFLAC_SOURCE_DIR}/src/lpc.cpp)
else()
set(CHDR_FLAC_SRC ${LIBCHDR_ROOT}/src/libchdr_flac.c)
endif()

add_executable(rp2350-bench
benchmark_main.c
hw_config.c
${CHDR_FLAC_SRC}
${LIBCHDR_ROOT}/src/libchdr_bitstream.c
${LIBCHDR_ROOT}/src/libchdr_cdrom.c
${LIBCHDR_ROOT}/src/libchdr_chd.c
Expand All @@ -58,7 +81,6 @@ add_executable(rp2350-bench
${LIBCHDR_ROOT}/src/libchdr_codec_lzma.c
${LIBCHDR_ROOT}/src/libchdr_codec_zlib.c
${LIBCHDR_ROOT}/src/libchdr_codec_zstd.c
${LIBCHDR_ROOT}/src/libchdr_flac.c
${LIBCHDR_ROOT}/src/libchdr_huffman.c
${LIBCHDR_ROOT}/deps/lzma-26.02/src/LzmaDec.c
${LIBCHDR_ROOT}/deps/miniz-3.1.2/miniz.c
Expand All @@ -67,8 +89,23 @@ add_executable(rp2350-bench

target_include_directories(rp2350-bench PRIVATE
${LIBCHDR_ROOT}/include
${LIBCHDR_ROOT}/src
${CMAKE_CURRENT_LIST_DIR}
)
if(CHDR_FLAC_BACKEND STREQUAL "microflac")
# Only micro-flac's public include dir goes on the target path. Its src/
# dir must NOT: it holds a crc.h that shadows the SD driver's own, and a C
# translation unit then pulls in a C++ header. micro-flac's sources find
# their private headers relative to themselves.
target_include_directories(rp2350-bench PRIVATE
${CHDR_MICROFLAC_SOURCE_DIR}/include)
target_compile_definitions(rp2350-bench PRIVATE
CHDR_FLAC_BACKEND_MICROFLAC MICRO_FLAC_DISABLE_OGG)
target_compile_options(rp2350-bench PRIVATE
$<$<COMPILE_LANGUAGE:CXX>:-fno-exceptions>
$<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>
$<$<COMPILE_LANGUAGE:CXX>:-fno-threadsafe-statics>)
endif()

target_compile_definitions(rp2350-bench PRIVATE
WANT_RAW_DATA_SECTOR=1
Expand Down
Loading
Loading