summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.travis.yml65
-rw-r--r--CMakeLists.txt115
-rw-r--r--Doxyfile2430
-rw-r--r--LICENSE2
-rw-r--r--README.md70
-rw-r--r--cmake/FindSDL2.cmake54
-rw-r--r--cmake/Findcunit.cmake41
-rw-r--r--debian/.git-dpm14
-rw-r--r--debian/changelog23
-rw-r--r--debian/control7
-rw-r--r--debian/libsdl-kitchensink1.install (renamed from debian/libsdl-kitchensink0.install)0
-rw-r--r--debian/libsdl-kitchensink1.symbols (renamed from debian/libsdl-kitchensink0.symbols)18
-rw-r--r--doc/Doxyfile1892
-rw-r--r--doc/FRONT.md43
-rw-r--r--doc/generate.sh3
-rw-r--r--doxygen-custom.css3
-rw-r--r--examples/example_audio.c61
-rw-r--r--examples/example_complex.c (renamed from examples/example_video.c)245
-rw-r--r--examples/example_custom.c230
-rw-r--r--examples/example_rwops.c223
-rw-r--r--examples/example_simple.c208
-rw-r--r--include/kitchensink/internal/audio/kitaudio.h11
-rw-r--r--include/kitchensink/internal/kitdecoder.h75
-rw-r--r--include/kitchensink/internal/kitlibstate.h8
-rw-r--r--include/kitchensink/internal/kitlist.h26
-rw-r--r--include/kitchensink/internal/libass.h66
-rw-r--r--include/kitchensink/internal/subtitle/kitatlas.h40
-rw-r--r--include/kitchensink/internal/subtitle/kitsubtitle.h17
-rw-r--r--include/kitchensink/internal/subtitle/kitsubtitlepacket.h22
-rw-r--r--include/kitchensink/internal/subtitle/renderers/kitsubass.h11
-rw-r--r--include/kitchensink/internal/subtitle/renderers/kitsubimage.h11
-rw-r--r--include/kitchensink/internal/subtitle/renderers/kitsubrenderer.h32
-rw-r--r--include/kitchensink/internal/utils/kitbuffer.h (renamed from include/kitchensink/internal/kitbuffer.h)2
-rw-r--r--include/kitchensink/internal/utils/kithelpers.h11
-rw-r--r--include/kitchensink/internal/utils/kitlog.h11
-rw-r--r--include/kitchensink/internal/utils/kitringbuffer.h (renamed from include/kitchensink/internal/kitringbuffer.h)3
-rw-r--r--include/kitchensink/internal/video/kitvideo.h13
-rw-r--r--include/kitchensink/kitchensink.h10
-rw-r--r--include/kitchensink/kitcodec.h32
-rw-r--r--include/kitchensink/kitconfig.h8
-rw-r--r--include/kitchensink/kiterror.h24
-rw-r--r--include/kitchensink/kitformat.h33
-rw-r--r--include/kitchensink/kitlib.h107
-rw-r--r--include/kitchensink/kitplayer.h393
-rw-r--r--include/kitchensink/kitsource.h212
-rw-r--r--include/kitchensink/kitutils.h28
-rw-r--r--pkg-config.pc.in12
-rw-r--r--sonar-project.properties7
-rw-r--r--src/internal/audio/kitaudio.c321
-rw-r--r--src/internal/kitdecoder.c282
-rw-r--r--src/internal/kitlibstate.c (renamed from src/kitlibstate.c)3
-rw-r--r--src/internal/libass.c26
-rw-r--r--src/internal/subtitle/kitatlas.c187
-rw-r--r--src/internal/subtitle/kitsubtitle.c190
-rw-r--r--src/internal/subtitle/kitsubtitlepacket.c23
-rw-r--r--src/internal/subtitle/renderers/kitsubass.c212
-rw-r--r--src/internal/subtitle/renderers/kitsubimage.c144
-rw-r--r--src/internal/subtitle/renderers/kitsubrenderer.c48
-rw-r--r--src/internal/utils/kitbuffer.c (renamed from src/kitbuffer.c)27
-rw-r--r--src/internal/utils/kithelpers.c30
-rw-r--r--src/internal/utils/kitringbuffer.c (renamed from src/kitringbuffer.c)6
-rw-r--r--src/internal/video/kitvideo.c301
-rw-r--r--src/kiterror.c4
-rw-r--r--src/kitlib.c112
-rw-r--r--src/kitlist.c79
-rw-r--r--src/kitplayer.c1572
-rw-r--r--src/kitsource.c173
-rw-r--r--src/kitutils.c4
-rw-r--r--tests/CMakeLists.txt18
-rw-r--r--tests/data/CEP140_512kb.mp4bin3628135 -> 0 bytes
-rw-r--r--tests/test_lib.c28
-rw-r--r--tests/test_source.c48
-rw-r--r--travis/ffmpeg.sh12
-rw-r--r--travis/sdl2.sh13
75 files changed, 6832 insertions, 4006 deletions
diff --git a/.gitignore b/.gitignore
index 673b0ca..100120e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,5 +36,4 @@
# Other
build/
-doc/doxygen_sqlite3.db
-doc/html
+docs/ \ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 89a45bf..2dc6271 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,59 +1,38 @@
language: c
-sudo: false
-
-env:
- global:
- - secure: "p7KivdPu+pWYLI95Rmtu8FgOPWUq36TiXr9kH5rDyuqg2iz3DAt8unDYXWGfSn9p6OzfchBd2WsGMMvbvICfBNm+uOjrQwWwW6SBENLNyGDiGKQd4coAmUF247UXY+CslRQrSuciajrNSrBIPOL4EjvlMmV8N5kwDrFB8QktgxyLf35vbNAc8vkoUdFu7vUzt2MISIA1rrWcOUjDaM6vqovzosr/LzDwwjj3HX92ys2YZh2m1V7BOMLuNRLW7pou3ge4VmXdTVIyFcEEG1Nqk1apeX9ZEsw0IQ6xLfcOJbqg5CSIFrkGhdlM1L+u7ThBBa+EZU9EZr6qYnVvHMRU1xyCFEJvGMr9FhS9ZnzPxOxz0BG/qkDbtkQtPtkPobb/325b7/jpDNZFXj6/kErdLjsClP1jUnFmj3jkcOXYsGlz4Osl7tih4a52VLWhzL7Lz7XxmZ9KUOJPuofg2CT0eUa1w+OAtA/tB7molRo2AowmfuN+PvNcmE16cFtveFqAArnN0R7st91SkyGZNRTumDfb7rh9coAboFEZU7vKYnCk7Tt7Atp43HnqnX0ywvxqEmHY7yYQQE1Z/1Mw0+6JnHuEaFp0Q5aU/KJmGXae6v0HqvGqvr8mCjsm0LMAxtRb6tb5zbtGCKwEd3HaDA/i3B9xLDBebbIxjMBjDdvAn9Q="
- - CMAKE_PREFIX_PATH=$HOME/local
-
-cache:
- directories:
- - $HOME/local
+sudo: required
+dist: trusty
compiler:
- gcc
-install:
- - chmod +x ${TRAVIS_BUILD_DIR}/travis/sdl2.sh
- - chmod +x ${TRAVIS_BUILD_DIR}/travis/ffmpeg.sh
- - ${TRAVIS_BUILD_DIR}/travis/sdl2.sh
- - ${TRAVIS_BUILD_DIR}/travis/ffmpeg.sh
-
-script: if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then cd $TRAVIS_BUILD_DIR && cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/bin/gcc-5 -DBUILD_EXAMPLES=On -DBUILD_TESTS=Off . && make; fi
+script:
+ - wget https://www.libsdl.org/release/SDL2-2.0.8.tar.gz -O - | tar -xz
+ - cd SDL2-2.0.8 && CC=gcc-7 ./configure --prefix=/usr && make -j2 && sudo make install && cd ..
+ - cmake -DCMAKE_BUILD_TYPE=Debug -DBUILD_EXAMPLES=On -DUSE_DYNAMIC_LIBASS=On -DCMAKE_C_COMPILER=/usr/bin/gcc-7 .
+ - build-wrapper-linux-x86-64 --out-dir bw-output make && sonar-scanner
notifications:
email: false
addons:
- coverity_scan:
- project:
- name: "katajakasa/SDL_kitchensink"
- description: "A Simple SDL2 / FFmpeg library for audio/video playback written in C99"
- notification_email: katajakasa@gmail.com
- build_command_prepend: "cov-configure --comptype gcc --compiler gcc-5 --template && cmake -DCMAKE_C_COMPILER=/usr/bin/gcc-5 -DCMAKE_BUILD_TYPE=Debug ."
- build_command: "make"
- branch_pattern: coverity_scan
+ sonarcloud:
+ organization: "katajakasa-github"
apt:
sources:
- - ubuntu-toolchain-r-test
- - george-edison55-precise-backports
+ - sourceline: 'ppa:ubuntu-toolchain-r/test'
+ - sourceline: 'ppa:george-edison55/cmake-3.x'
+ - sourceline: 'ppa:jonathonf/ffmpeg-3'
packages:
- - libc6-dev
- - yasm
- - gcc-5
- - gettext
- - libcunit1
- - libcunit1-dev
- - libasound2-dev
- - libpulse-dev
- - libx11-dev
- - libxext-dev
- - libxrandr-dev
- - libxi-dev
- - libxxf86vm-dev
- - libxss-dev
- - libudev-dev
- cmake
- - cmake-data
+ - gcc-7
- libass-dev
+ - libavcodec-dev
+ - libavformat-dev
+ - libswresample-dev
+ - libswscale-dev
+ - libavutil-dev
+
+cache:
+ directories:
+ - '$HOME/.sonar/cache'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7ed58af..c89dbc3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,10 +1,11 @@
-cmake_minimum_required(VERSION 3.0)
+cmake_minimum_required(VERSION 3.7)
project(SDL_kitchensink C)
+include(GNUInstallDirs)
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
-set(KIT_VERSION_MAJOR "0")
+set(KIT_VERSION_MAJOR "1")
set(KIT_VERSION_MINOR "0")
-set(KIT_VERSION_PATCH "7")
+set(KIT_VERSION_PATCH "4")
set(KIT_VERSION ${KIT_VERSION_MAJOR}.${KIT_VERSION_MINOR}.${KIT_VERSION_PATCH})
add_definitions(
-DKIT_VERSION_MAJOR=${KIT_VERSION_MAJOR}
@@ -12,76 +13,112 @@ add_definitions(
-DKIT_VERSION_PATCH=${KIT_VERSION_PATCH}
)
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=c99")
-set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -pedantic -Werror -fno-omit-frame-pointer")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -Werror -fno-omit-frame-pointer -Wno-deprecated-declarations")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -ggdb -O2 -fno-omit-frame-pointer -DNDEBUG")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -DNDEBUG")
set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -Os -DNDEBUG")
option(BUILD_EXAMPLES "Build examples" OFF)
-option(BUILD_TESTS "Build unittests" OFF)
+option(USE_DYNAMIC_LIBASS "Use dynamically loaded libass" ON)
+option(USE_ASAN "Use AddressSanitizer" OFF)
-find_package(SDL2)
-find_package(ass)
+find_package(SDL2 REQUIRED)
find_package(ffmpeg COMPONENTS avcodec avformat avutil swscale swresample)
-if(BUILD_TESTS)
- add_subdirectory(tests)
-endif()
-
-include_directories(
+set(LIBRARIES
+ ${SDL2_LIBRARIES}
+ ${FFMPEG_LIBRARIES}
+)
+set(INCLUDES
include/
${SDL2_INCLUDE_DIRS}
${FFMPEG_INCLUDE_DIRS}
- ${ASS_INCLUDE_DIRS}
)
-FILE(GLOB SOURCES "src/*.c")
-FILE(GLOB HEADERS "include/kitchensink/*.h")
+if(USE_DYNAMIC_LIBASS)
+ if(WIN32 OR MINGW OR MSYS)
+ set(DYNAMIC_LIBASS_NAME "\"libass-9.dll\"")
+ else()
+ set(DYNAMIC_LIBASS_NAME "\"libass.so\"")
+ endif()
+ add_definitions(-DUSE_DYNAMIC_LIBASS)
+ add_definitions(-DDYNAMIC_LIBASS_NAME=${DYNAMIC_LIBASS_NAME})
+else()
+ find_package(ass)
+ set(LIBRARIES ${LIBRARIES} ${ASS_LIBRARIES})
+ set(INCLUDES ${INCLUDES} ${ASS_INCLUDE_DIRS})
+endif()
+
+FILE(GLOB_RECURSE SOURCES "src/*.c")
+FILE(GLOB INSTALL_HEADERS "include/kitchensink/*.h")
add_library(SDL_kitchensink SHARED ${SOURCES})
add_library(SDL_kitchensink_static STATIC ${SOURCES})
set_target_properties(SDL_kitchensink PROPERTIES VERSION ${KIT_VERSION})
set_target_properties(SDL_kitchensink PROPERTIES SOVERSION ${KIT_VERSION_MAJOR})
+
set_target_properties(SDL_kitchensink PROPERTIES DEBUG_POSTFIX "d")
set_target_properties(SDL_kitchensink_static PROPERTIES DEBUG_POSTFIX "d")
+
target_compile_definitions(SDL_kitchensink PRIVATE "KIT_DLL;KIT_DLL_EXPORTS")
target_compile_options(SDL_kitchensink PRIVATE "-fvisibility=hidden")
-target_link_libraries(SDL_kitchensink
- ${SDL2_LIBRARIES}
- ${FFMPEG_LIBRARIES}
- ${ASS_LIBRARIES}
+set_property(TARGET SDL_kitchensink PROPERTY C_STANDARD 99)
+set_property(TARGET SDL_kitchensink_static PROPERTY C_STANDARD 99)
+
+if(USE_ASAN)
+ set(LIBRARIES asan ${LIBRARIES})
+ target_compile_options(SDL_kitchensink PRIVATE "-fsanitize=address")
+ message(STATUS "DEVELOPMENT: AddressSanitizer enabled!")
+endif()
+
+include_directories(${INCLUDES})
+target_link_libraries(SDL_kitchensink ${LIBRARIES})
+
+set(PKG_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/SDL_kitchensink.pc")
+
+configure_file(
+ "${CMAKE_CURRENT_SOURCE_DIR}/pkg-config.pc.in"
+ ${PKG_CONFIG_FILE}
+ @ONLY
)
if(BUILD_EXAMPLES)
- add_executable(exampleaudio examples/example_audio.c)
- add_executable(examplevideo examples/example_video.c)
+ add_executable(audio examples/example_audio.c)
+ add_executable(complex examples/example_complex.c)
+ add_executable(simple examples/example_simple.c)
+ add_executable(custom examples/example_custom.c)
+ add_executable(rwops examples/example_rwops.c)
if(MINGW)
- target_link_libraries(exampleaudio mingw32)
- target_link_libraries(examplevideo mingw32)
+ target_link_libraries(audio mingw32)
+ target_link_libraries(complex mingw32)
+ target_link_libraries(simple mingw32)
+ target_link_libraries(custom mingw32)
+ target_link_libraries(rwops mingw32)
endif()
- target_link_libraries(exampleaudio
- SDL_kitchensink_static
- ${SDL2_LIBRARIES}
- ${FFMPEG_LIBRARIES}
- ${ASS_LIBRARIES}
- )
- target_link_libraries(examplevideo
- SDL_kitchensink_static
- ${SDL2_LIBRARIES}
- ${FFMPEG_LIBRARIES}
- ${ASS_LIBRARIES}
- )
+ set_property(TARGET audio PROPERTY C_STANDARD 99)
+ set_property(TARGET complex PROPERTY C_STANDARD 99)
+ set_property(TARGET simple PROPERTY C_STANDARD 99)
+ set_property(TARGET custom PROPERTY C_STANDARD 99)
+ set_property(TARGET rwops PROPERTY C_STANDARD 99)
+
+ target_link_libraries(audio SDL_kitchensink_static ${LIBRARIES})
+ target_link_libraries(complex SDL_kitchensink_static ${LIBRARIES})
+ target_link_libraries(simple SDL_kitchensink_static ${LIBRARIES})
+ target_link_libraries(custom SDL_kitchensink_static ${LIBRARIES})
+ target_link_libraries(rwops SDL_kitchensink_static ${LIBRARIES})
endif()
-# Installation
-include(GNUInstallDirs)
+# documentation target
+add_custom_target(docs COMMAND doxygen WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
-INSTALL(FILES ${HEADERS} DESTINATION include/kitchensink/)
+# Installation
+install(FILES ${PKG_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+install(FILES ${INSTALL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/kitchensink)
INSTALL(TARGETS SDL_kitchensink SDL_kitchensink_static
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
diff --git a/Doxyfile b/Doxyfile
new file mode 100644
index 0000000..db85229
--- /dev/null
+++ b/Doxyfile
@@ -0,0 +1,2430 @@
+# Doxyfile 1.8.13
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = "SDL_kitchensink"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = docs/
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS = NO
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF = "The $name class" \
+ "The $name widget" \
+ "The $name file" \
+ is \
+ provides \
+ specifies \
+ contains \
+ represents \
+ a \
+ an \
+ the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 0.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 0
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO, these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES, upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong or incomplete
+# parameter documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered.
+# The default value is: NO.
+
+WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = include/kitchensink/ .
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,
+# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.
+
+FILE_PATTERNS = *.h *.md
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = examples/
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = README.md
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET = doxygen-custom.css
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the master .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empty
+# string, for the replacement values of the other commands the user is referred
+# to HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES, to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
+# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code
+# with syntax highlighting in the RTF output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sf.net) file that captures the
+# structure of the code including all documentation. Note that this feature is
+# still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP = YES
diff --git a/LICENSE b/LICENSE
index bec844e..04f8a3d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 Tuomas Virtanen
+Copyright (c) 2018 Tuomas Virtanen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 037626a..3b9b467 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,24 @@
# SDL_kitchensink
[![Build Status](https://travis-ci.org/katajakasa/SDL_kitchensink.svg?branch=master)](https://travis-ci.org/katajakasa/SDL_kitchensink)
-[![Coverity Scan Build Status](https://scan.coverity.com/projects/7585/badge.svg)](https://scan.coverity.com/projects/katajakasa-sdl_kitchensink)
+[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=sdl_kitchensink&metric=alert_status)](https://sonarcloud.io/dashboard?id=sdl_kitchensink)
FFmpeg and SDL2 based library for audio and video playback, written in C99.
-This library is still very much todo, but it's slowly getting there.
+Documentation is available at http://katajakasa.github.io/SDL_kitchensink/
Features:
-* Decoding video & audio via FFmpeg
-* Dumping video data on SDL_textures
+* Decoding video, audio and subtitles via FFmpeg
+* Dumping video and subtitle data on SDL_textures
* Dumping audio data in the usual mono/stereo interleaved formats
* Automatic audio and video conversion to SDL2 friendly formats
* Synchronizing video & audio to clock
* Seeking forwards and backwards
-* Bitmap & libass subtitle support. No text (srt, sub) support yet.
+* Bitmap, text and SSA/ASS subtitle support
+
+Note! Master branch is for the development of v1.0.0 series. v0 can be found in the
+rel-kitchensink-0 branch. v0 is no longer in active development and only bug- and security-fixes
+are accepted.
## 1. Library requirements
@@ -23,9 +27,9 @@ Build requirements:
* GCC (C99 support required)
Library requirements:
-* SDL2 (>=2.0.3) (Note! Examples require 2.0.4!)
+* SDL2 (>=2.0.5)
* FFmpeg (>=3.0)
-* libass
+* libass (optional, supports runtime linking via SDL_LoadSO)
* CUnit (optional, for unittests)
Note that Clang might work, but is not tested. Older SDL2 and FFmpeg library versions
@@ -76,26 +80,56 @@ Change CMAKE_INSTALL_PREFIX as necessary to change the installation path. The fi
Just add ```-DBUILD_EXAMPLES=1``` to cmake arguments and rebuild.
-### 2.4. Building unittests
-
-Make sure CUnit is installed, then add ```-DBUILD_UNITTESTS=1``` to the cmake arguments and rebuild.
-
-You can run unittests by running ```make unittest```.
+### 2.4. Building with AddressSanitizer
-## 3. License
+This is for development/debugging use only!
-MIT. Please see ```LICENSE``` for details.
+Make sure llvm is installed, then add ```-DUSE_ASAN=1``` to the cmake arguments and rebuild. Note that ASAN is not
+supported on all OSes (eg. windows).
-Note that FFmpeg has a rather complex license. Please take a look at [FFmpeg Legal page](http://ffmpeg.org/legal.html)
-for details.
+After building, you can run with the following (make sure to set correct llvm-symbolizer path):
+```
+ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer ./examplevideo <my videofile>
+```
-## 4. Why SDL_kitchensink
+## 3. Why SDL_kitchensink
Because pulling major blob of library code like ffmpeg feels like bringing in a whole house with its
kitchensink and everything to the project. Also, it sounded funny. Also, SDL_ffmpeg is already reserved :(
-## 5. Examples
+## 4. Examples
Please see examples directory. You can also take a look at unittests for some help.
Note that examples are NOT meant for any kind of real life use; they are only meant to
show simple use cases for the library.
+
+## 5. FFMPEG & licensing
+
+Note that FFmpeg has a rather complex license. Please take a look at
+[FFmpeg Legal page](http://ffmpeg.org/legal.html) for details.
+
+## 6. License
+
+```
+The MIT License (MIT)
+
+Copyright (c) 2018 Tuomas Virtanen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
deleted file mode 100644
index 8102832..0000000
--- a/cmake/FindSDL2.cmake
+++ /dev/null
@@ -1,54 +0,0 @@
-# A Simple SDL2 Finder.
-# (c) Tuomas Virtanen 2016 (Licensed under MIT license)
-# Usage:
-# find_package(SDL2)
-#
-# Declares:
-# * SDL2_FOUND
-# * SDL2_INCLUDE_DIRS
-# * SDL2_LIBRARIES
-#
-
-set(SDL2_SEARCH_PATHS
- /usr/local/
- /usr/
- /opt
-)
-
-find_path(SDL2_INCLUDE_DIR SDL2/SDL.h
- HINTS
- PATH_SUFFIXES include
- PATHS ${SDL2_SEARCH_PATHS}
-)
-
-find_library(SDL2_LIBRARY
- NAMES SDL2
- HINTS
- PATH_SUFFIXES lib
- PATHS ${SDL2_SEARCH_PATHS}
-)
-
-if(MINGW)
- find_library(SDL2MAIN_LIBRARY
- NAMES SDL2main
- HINTS
- PATH_SUFFIXES lib
- PATHS ${SDL2_SEARCH_PATHS}
- )
-else()
- set(SDL2MAIN_LIBRARY "")
-endif()
-
-if(SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
- set(SDL2_FOUND TRUE)
-endif()
-
-if(SDL2_FOUND)
- set(SDL2_LIBRARIES ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY})
- set(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
- message(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
-else()
- message(WARNING "Could not find SDL2")
-endif()
-
-mark_as_advanced(SDL2MAIN_LIBRARY SDL2_LIBRARY SDL2_INCLUDE_DIR SDL2_SEARCH_PATHS)
diff --git a/cmake/Findcunit.cmake b/cmake/Findcunit.cmake
deleted file mode 100644
index ae81cf1..0000000
--- a/cmake/Findcunit.cmake
+++ /dev/null
@@ -1,41 +0,0 @@
-# A Simple CUnit Finder.
-# (c) Tuomas Virtanen 2016 (Licensed under MIT license)
-# Usage:
-# find_package(cunit)
-#
-# Declares:
-# * CUNIT_FOUND
-# * CUNIT_INCLUDE_DIRS
-# * CUNIT_LIBRARIES
-#
-
-set(CUNIT_SEARCH_PATHS
- /usr/local/
- /usr
- /opt
-)
-
-find_path(CUNIT_INCLUDE_DIR CUnit/CUnit.h
- HINTS
- PATH_SUFFIXES include
- PATHS ${CUNIT_SEARCH_PATHS}
-)
-find_library(CUNIT_LIBRARY cunit
- HINTS
- PATH_SUFFIXES lib
- PATHS ${CUNIT_SEARCH_PATHS}
-)
-
-if(CUNIT_INCLUDE_DIR AND CUNIT_LIBRARY)
- set(CUNIT_FOUND TRUE)
-endif()
-
-if(CUNIT_FOUND)
- set(CUNIT_LIBRARIES ${CUNIT_LIBRARY})
- set(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR})
- message(STATUS "Found CUnit: ${CUNIT_LIBRARY}")
-else()
- message(WARNING "Could not find CUnit.")
-endif()
-
-mark_as_advanced(CUNIT_LIBRARY CUNIT_INCLUDE_DIR)
diff --git a/debian/.git-dpm b/debian/.git-dpm
index cc8f7cb..9074f07 100644
--- a/debian/.git-dpm
+++ b/debian/.git-dpm
@@ -1,8 +1,8 @@
# see git-dpm(1) from git-dpm package
-48c7daa8e91d96805548ef4d2fa9f4b925c26108
-48c7daa8e91d96805548ef4d2fa9f4b925c26108
-48c7daa8e91d96805548ef4d2fa9f4b925c26108
-48c7daa8e91d96805548ef4d2fa9f4b925c26108
-sdl-kitchensink_0.0.7.orig.tar.gz
-8732427fdaf541cc768d9a26e426d371c74d3753
-3646266
+4c38cd19867186bb97c179eab4cc72a426b795df
+4c38cd19867186bb97c179eab4cc72a426b795df
+4c38cd19867186bb97c179eab4cc72a426b795df
+4c38cd19867186bb97c179eab4cc72a426b795df
+sdl-kitchensink_1.0.4.orig.tar.gz
+e82045206e9ca1b9478a5488ea31d4e5016cc161
+67884
diff --git a/debian/changelog b/debian/changelog
index 7dbdf2b..f358f5d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,26 @@
+sdl-kitchensink (1.0.4-2) unstable; urgency=medium
+
+ * Upload to unstable
+
+ -- Didier Raboud <odyx@debian.org> Mon, 17 Sep 2018 10:06:08 +0200
+
+sdl-kitchensink (1.0.4-1) experimental; urgency=medium
+
+ * New 1.0.4 upstream release
+ - Add Kit_CreateSourceFromRW symbol
+ * Bump S-V to 4.2.1 without changes needed
+
+ -- Didier Raboud <odyx@debian.org> Mon, 03 Sep 2018 11:48:01 +0200
+
+sdl-kitchensink (1.0.2-1) experimental; urgency=low
+
+ * New 1.0.2 upstream release
+ * Bump S-V to 4.1.4 without changes needed
+ * Bump libsdl-kitchensink SOVERSION from 0 to 1, refresh symbols
+ * Add libsdl2-dev Depends to libsdl-kitchensink-dev
+
+ -- Didier Raboud <odyx@debian.org> Mon, 02 Jul 2018 08:52:43 +0200
+
sdl-kitchensink (0.0.7-3) unstable; urgency=medium
* Cleanup d/control thanks to `cme update dpkg-control`
diff --git a/debian/control b/debian/control
index 74f5d73..8f51232 100644
--- a/debian/control
+++ b/debian/control
@@ -15,12 +15,12 @@ Build-Depends: debhelper (>= 10.2.5~),
libswscale-dev,
libpostproc-dev,
libass-dev
-Standards-Version: 4.1.3
+Standards-Version: 4.2.1
Vcs-Browser: https://salsa.debian.org/debian/sdl-kitchensink
Vcs-Git: https://salsa.debian.org/debian/sdl-kitchensink.git
Homepage: https://github.com/katajakasa/SDL_kitchensink
-Package: libsdl-kitchensink0
+Package: libsdl-kitchensink1
Architecture: any
Multi-Arch: same
Depends: ${shlibs:Depends},
@@ -41,7 +41,8 @@ Package: libsdl-kitchensink-dev
Architecture: any
Multi-Arch: same
Section: libdevel
-Depends: libsdl-kitchensink0 (= ${binary:Version}),
+Depends: libsdl-kitchensink1 (= ${binary:Version}),
+ libsdl2-dev,
${misc:Depends}
Description: FFmpeg and SDL2 based library for audio and video playback - Development files
It provides FFmpeg-based audio and video playback for SDL which features:
diff --git a/debian/libsdl-kitchensink0.install b/debian/libsdl-kitchensink1.install
index 79f436c..79f436c 100644
--- a/debian/libsdl-kitchensink0.install
+++ b/debian/libsdl-kitchensink1.install
diff --git a/debian/libsdl-kitchensink0.symbols b/debian/libsdl-kitchensink1.symbols
index d0edaac..d5e54a0 100644
--- a/debian/libsdl-kitchensink0.symbols
+++ b/debian/libsdl-kitchensink1.symbols
@@ -1,25 +1,30 @@
-libSDL_kitchensink.so.0 libsdl-kitchensink0 #MINVER#
+libSDL_kitchensink.so.1 libsdl-kitchensink1 #MINVER#
Kit_ClearError@Base 0.0.6
Kit_ClosePlayer@Base 0.0.6
Kit_CloseSource@Base 0.0.6
Kit_CreatePlayer@Base 0.0.6
+ Kit_CreateSourceFromCustom@Base 1.0.2
+ Kit_CreateSourceFromRW@Base 1.0.4
Kit_CreateSourceFromUrl@Base 0.0.6
- Kit_GetAudioData@Base 0.0.6
Kit_GetBestSourceStream@Base 0.0.6
Kit_GetError@Base 0.0.6
+ Kit_GetHint@Base 1.0.2
Kit_GetKitStreamTypeString@Base 0.0.6
+ Kit_GetPlayerAudioData@Base 1.0.2
+ Kit_GetPlayerAudioStream@Base 1.0.2
Kit_GetPlayerDuration@Base 0.0.6
Kit_GetPlayerInfo@Base 0.0.6
Kit_GetPlayerPosition@Base 0.0.6
Kit_GetPlayerState@Base 0.0.6
+ Kit_GetPlayerSubtitleData@Base 1.0.2
+ Kit_GetPlayerSubtitleStream@Base 1.0.2
+ Kit_GetPlayerVideoData@Base 1.0.2
+ Kit_GetPlayerVideoStream@Base 1.0.2
Kit_GetSDLAudioFormatString@Base 0.0.6
Kit_GetSDLPixelFormatString@Base 0.0.6
- Kit_GetSourceStream@Base 0.0.6
Kit_GetSourceStreamCount@Base 0.0.6
Kit_GetSourceStreamInfo@Base 0.0.6
- Kit_GetSubtitleData@Base 0.0.6
Kit_GetVersion@Base 0.0.6
- Kit_GetVideoData@Base 0.0.6
Kit_Init@Base 0.0.6
Kit_PlayerPause@Base 0.0.6
Kit_PlayerPlay@Base 0.0.6
@@ -27,4 +32,5 @@ libSDL_kitchensink.so.0 libsdl-kitchensink0 #MINVER#
Kit_PlayerStop@Base 0.0.6
Kit_Quit@Base 0.0.6
Kit_SetError@Base 0.0.6
- Kit_SetSourceStream@Base 0.0.6
+ Kit_SetHint@Base 1.0.2
+ Kit_SetPlayerScreenSize@Base 1.0.2
diff --git a/doc/Doxyfile b/doc/Doxyfile
deleted file mode 100644
index 7bb5ca0..0000000
--- a/doc/Doxyfile
+++ /dev/null
@@ -1,1892 +0,0 @@
-# Doxyfile 1.8.3.1
-
-#---------------------------------------------------------------------------
-# Project related configuration options
-#---------------------------------------------------------------------------
-
-# This tag specifies the encoding used for all characters in the config file
-# that follow. The default is UTF-8 which is also the encoding used for all
-# text before the first occurrence of this tag. Doxygen uses libiconv (or the
-# iconv built into libc) for the transcoding. See
-# http://www.gnu.org/software/libiconv for the list of possible encodings.
-
-DOXYFILE_ENCODING = UTF-8
-
-# The PROJECT_NAME tag is a single word (or sequence of words) that should
-# identify the project. Note that if you do not use Doxywizard you need
-# to put quotes around the project name if it contains spaces.
-
-PROJECT_NAME = SDL_kitchensink
-
-# The PROJECT_NUMBER tag can be used to enter a project or revision number.
-# This could be handy for archiving the generated documentation or
-# if some version control system is used.
-
-PROJECT_NUMBER =
-
-# Using the PROJECT_BRIEF tag one can provide an optional one line description
-# for a project that appears at the top of each page and should give viewer
-# a quick idea about the purpose of the project. Keep the description short.
-
-PROJECT_BRIEF = "A Simple SDL2 / FFmpeg library for audio/video playback written in C99"
-
-# With the PROJECT_LOGO tag one can specify an logo or icon that is
-# included in the documentation. The maximum height of the logo should not
-# exceed 55 pixels and the maximum width should not exceed 200 pixels.
-# Doxygen will copy the logo to the output directory.
-
-PROJECT_LOGO =
-
-# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
-# base path where the generated documentation will be put.
-# If a relative path is entered, it will be relative to the location
-# where doxygen was started. If left blank the current directory will be used.
-
-OUTPUT_DIRECTORY = .
-
-# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
-# 4096 sub-directories (in 2 levels) under the output directory of each output
-# format and will distribute the generated files over these directories.
-# Enabling this option can be useful when feeding doxygen a huge amount of
-# source files, where putting all generated files in the same directory would
-# otherwise cause performance problems for the file system.
-
-CREATE_SUBDIRS = NO
-
-# The OUTPUT_LANGUAGE tag is used to specify the language in which all
-# documentation generated by doxygen is written. Doxygen will use this
-# information to generate all constant output in the proper language.
-# The default language is English, other supported languages are:
-# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
-# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
-# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
-# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
-# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
-
-OUTPUT_LANGUAGE = English
-
-# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
-# include brief member descriptions after the members that are listed in
-# the file and class documentation (similar to JavaDoc).
-# Set to NO to disable this.
-
-BRIEF_MEMBER_DESC = YES
-
-# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
-# the brief description of a member or function before the detailed description.
-# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
-# brief descriptions will be completely suppressed.
-
-REPEAT_BRIEF = YES
-
-# This tag implements a quasi-intelligent brief description abbreviator
-# that is used to form the text in various listings. Each string
-# in this list, if found as the leading text of the brief description, will be
-# stripped from the text and the result after processing the whole list, is
-# used as the annotated text. Otherwise, the brief description is used as-is.
-# If left blank, the following values are used ("$name" is automatically
-# replaced with the name of the entity): "The $name class" "The $name widget"
-# "The $name file" "is" "provides" "specifies" "contains"
-# "represents" "a" "an" "the"
-
-ABBREVIATE_BRIEF = "The $name class" \
- "The $name widget" \
- "The $name file" \
- is \
- provides \
- specifies \
- contains \
- represents \
- a \
- an \
- the
-
-# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
-# Doxygen will generate a detailed section even if there is only a brief
-# description.
-
-ALWAYS_DETAILED_SEC = NO
-
-# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
-# inherited members of a class in the documentation of that class as if those
-# members were ordinary class members. Constructors, destructors and assignment
-# operators of the base classes will not be shown.
-
-INLINE_INHERITED_MEMB = NO
-
-# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
-# path before files name in the file list and in the header files. If set
-# to NO the shortest path that makes the file name unique will be used.
-
-FULL_PATH_NAMES = YES
-
-# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
-# can be used to strip a user-defined part of the path. Stripping is
-# only done if one of the specified strings matches the left-hand part of
-# the path. The tag can be used to show relative paths in the file list.
-# If left blank the directory from which doxygen is run is used as the
-# path to strip. Note that you specify absolute paths here, but also
-# relative paths, which will be relative from the directory where doxygen is
-# started.
-
-STRIP_FROM_PATH = ../..
-
-# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
-# the path mentioned in the documentation of a class, which tells
-# the reader which header file to include in order to use a class.
-# If left blank only the name of the header file containing the class
-# definition is used. Otherwise one should specify the include paths that
-# are normally passed to the compiler using the -I flag.
-
-STRIP_FROM_INC_PATH =
-
-# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful if your file system
-# doesn't support long names like on DOS, Mac, or CD-ROM.
-
-SHORT_NAMES = NO
-
-# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
-# will interpret the first line (until the first dot) of a JavaDoc-style
-# comment as the brief description. If set to NO, the JavaDoc
-# comments will behave just like regular Qt-style comments
-# (thus requiring an explicit @brief command for a brief description.)
-
-JAVADOC_AUTOBRIEF = NO
-
-# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
-# interpret the first line (until the first dot) of a Qt-style
-# comment as the brief description. If set to NO, the comments
-# will behave just like regular Qt-style comments (thus requiring
-# an explicit \brief command for a brief description.)
-
-QT_AUTOBRIEF = NO
-
-# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
-# treat a multi-line C++ special comment block (i.e. a block of //! or ///
-# comments) as a brief description. This used to be the default behaviour.
-# The new default is to treat a multi-line C++ comment block as a detailed
-# description. Set this tag to YES if you prefer the old behaviour instead.
-
-MULTILINE_CPP_IS_BRIEF = NO
-
-# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
-# member inherits the documentation from any documented member that it
-# re-implements.
-
-INHERIT_DOCS = YES
-
-# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
-# a new page for each member. If set to NO, the documentation of a member will
-# be part of the file/class/namespace that contains it.
-
-SEPARATE_MEMBER_PAGES = NO
-
-# The TAB_SIZE tag can be used to set the number of spaces in a tab.
-# Doxygen uses this value to replace tabs by spaces in code fragments.
-
-TAB_SIZE = 4
-
-# This tag can be used to specify a number of aliases that acts
-# as commands in the documentation. An alias has the form "name=value".
-# For example adding "sideeffect=\par Side Effects:\n" will allow you to
-# put the command \sideeffect (or @sideeffect) in the documentation, which
-# will result in a user-defined paragraph with heading "Side Effects:".
-# You can put \n's in the value part of an alias to insert newlines.
-
-ALIASES =
-
-# This tag can be used to specify a number of word-keyword mappings (TCL only).
-# A mapping has the form "name=value". For example adding
-# "class=itcl::class" will allow you to use the command class in the
-# itcl::class meaning.
-
-TCL_SUBST =
-
-# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
-# sources only. Doxygen will then generate output that is more tailored for C.
-# For instance, some of the names that are used will be different. The list
-# of all members will be omitted, etc.
-
-OPTIMIZE_OUTPUT_FOR_C = YES
-
-# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
-# sources only. Doxygen will then generate output that is more tailored for
-# Java. For instance, namespaces will be presented as packages, qualified
-# scopes will look different, etc.
-
-OPTIMIZE_OUTPUT_JAVA = NO
-
-# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
-# sources only. Doxygen will then generate output that is more tailored for
-# Fortran.
-
-OPTIMIZE_FOR_FORTRAN = NO
-
-# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
-# sources. Doxygen will then generate output that is tailored for
-# VHDL.
-
-OPTIMIZE_OUTPUT_VHDL = NO
-
-# Doxygen selects the parser to use depending on the extension of the files it
-# parses. With this tag you can assign which parser to use for a given
-# extension. Doxygen has a built-in mapping, but you can override or extend it
-# using this tag. The format is ext=language, where ext is a file extension,
-# and language is one of the parsers supported by doxygen: IDL, Java,
-# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C,
-# C++. For instance to make doxygen treat .inc files as Fortran files (default
-# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note
-# that for custom extensions you also need to set FILE_PATTERNS otherwise the
-# files are not read by doxygen.
-
-EXTENSION_MAPPING =
-
-# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
-# comments according to the Markdown format, which allows for more readable
-# documentation. See http://daringfireball.net/projects/markdown/ for details.
-# The output of markdown processing is further processed by doxygen, so you
-# can mix doxygen, HTML, and XML commands with Markdown formatting.
-# Disable only in case of backward compatibilities issues.
-
-MARKDOWN_SUPPORT = YES
-
-# When enabled doxygen tries to link words that correspond to documented classes,
-# or namespaces to their corresponding documentation. Such a link can be
-# prevented in individual cases by by putting a % sign in front of the word or
-# globally by setting AUTOLINK_SUPPORT to NO.
-
-AUTOLINK_SUPPORT = YES
-
-# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
-# to include (a tag file for) the STL sources as input, then you should
-# set this tag to YES in order to let doxygen match functions declarations and
-# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also makes the inheritance and collaboration
-# diagrams that involve STL classes more complete and accurate.
-
-BUILTIN_STL_SUPPORT = NO
-
-# If you use Microsoft's C++/CLI language, you should set this option to YES to
-# enable parsing support.
-
-CPP_CLI_SUPPORT = NO
-
-# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
-# Doxygen will parse them like normal C++ but will assume all classes use public
-# instead of private inheritance when no explicit protection keyword is present.
-
-SIP_SUPPORT = NO
-
-# For Microsoft's IDL there are propget and propput attributes to indicate
-# getter and setter methods for a property. Setting this option to YES (the
-# default) will make doxygen replace the get and set methods by a property in
-# the documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
-
-IDL_PROPERTY_SUPPORT = YES
-
-# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
-# tag is set to YES, then doxygen will reuse the documentation of the first
-# member in the group (if any) for the other members of the group. By default
-# all members of a group must be documented explicitly.
-
-DISTRIBUTE_GROUP_DOC = NO
-
-# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
-# the same type (for instance a group of public functions) to be put as a
-# subgroup of that type (e.g. under the Public Functions section). Set it to
-# NO to prevent subgrouping. Alternatively, this can be done per class using
-# the \nosubgrouping command.
-
-SUBGROUPING = YES
-
-# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
-# unions are shown inside the group in which they are included (e.g. using
-# @ingroup) instead of on a separate page (for HTML and Man pages) or
-# section (for LaTeX and RTF).
-
-INLINE_GROUPED_CLASSES = NO
-
-# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
-# unions with only public data fields will be shown inline in the documentation
-# of the scope in which they are defined (i.e. file, namespace, or group
-# documentation), provided this scope is documented. If set to NO (the default),
-# structs, classes, and unions are shown on a separate page (for HTML and Man
-# pages) or section (for LaTeX and RTF).
-
-INLINE_SIMPLE_STRUCTS = NO
-
-# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
-# is documented as struct, union, or enum with the name of the typedef. So
-# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
-# with name TypeT. When disabled the typedef will appear as a member of a file,
-# namespace, or class. And the struct will be named TypeS. This can typically
-# be useful for C code in case the coding convention dictates that all compound
-# types are typedef'ed and only the typedef is referenced, never the tag name.
-
-TYPEDEF_HIDES_STRUCT = NO
-
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penalty.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will roughly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-SYMBOL_CACHE_SIZE = 0
-
-# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
-# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
-# their name and scope. Since this can be an expensive process and often the
-# same symbol appear multiple times in the code, doxygen keeps a cache of
-# pre-resolved symbols. If the cache is too small doxygen will become slower.
-# If the cache is too large, memory is wasted. The cache size is given by this
-# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-LOOKUP_CACHE_SIZE = 0
-
-#---------------------------------------------------------------------------
-# Build related configuration options
-#---------------------------------------------------------------------------
-
-# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
-# documentation are documented, even if no documentation was available.
-# Private class members and static file members will be hidden unless
-# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
-
-EXTRACT_ALL = NO
-
-# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
-# will be included in the documentation.
-
-EXTRACT_PRIVATE = NO
-
-# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
-# scope will be included in the documentation.
-
-EXTRACT_PACKAGE = NO
-
-# If the EXTRACT_STATIC tag is set to YES all static members of a file
-# will be included in the documentation.
-
-EXTRACT_STATIC = NO
-
-# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
-# defined locally in source files will be included in the documentation.
-# If set to NO only classes defined in header files are included.
-
-EXTRACT_LOCAL_CLASSES = YES
-
-# This flag is only useful for Objective-C code. When set to YES local
-# methods, which are defined in the implementation section but not in
-# the interface are included in the documentation.
-# If set to NO (the default) only methods in the interface are included.
-
-EXTRACT_LOCAL_METHODS = NO
-
-# If this flag is set to YES, the members of anonymous namespaces will be
-# extracted and appear in the documentation as a namespace called
-# 'anonymous_namespace{file}', where file will be replaced with the base
-# name of the file that contains the anonymous namespace. By default
-# anonymous namespaces are hidden.
-
-EXTRACT_ANON_NSPACES = NO
-
-# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
-# undocumented members of documented classes, files or namespaces.
-# If set to NO (the default) these members will be included in the
-# various overviews, but no documentation section is generated.
-# This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_MEMBERS = NO
-
-# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
-# undocumented classes that are normally visible in the class hierarchy.
-# If set to NO (the default) these classes will be included in the various
-# overviews. This option has no effect if EXTRACT_ALL is enabled.
-
-HIDE_UNDOC_CLASSES = NO
-
-# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
-# friend (class|struct|union) declarations.
-# If set to NO (the default) these declarations will be included in the
-# documentation.
-
-HIDE_FRIEND_COMPOUNDS = NO
-
-# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
-# documentation blocks found inside the body of a function.
-# If set to NO (the default) these blocks will be appended to the
-# function's detailed documentation block.
-
-HIDE_IN_BODY_DOCS = NO
-
-# The INTERNAL_DOCS tag determines if documentation
-# that is typed after a \internal command is included. If the tag is set
-# to NO (the default) then the documentation will be excluded.
-# Set it to YES to include the internal documentation.
-
-INTERNAL_DOCS = NO
-
-# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
-# file names in lower-case letters. If set to YES upper-case letters are also
-# allowed. This is useful if you have classes or files whose names only differ
-# in case and if your file system supports case sensitive file names. Windows
-# and Mac users are advised to set this option to NO.
-
-CASE_SENSE_NAMES = NO
-
-# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
-# will show members with their full class and namespace scopes in the
-# documentation. If set to YES the scope will be hidden.
-
-HIDE_SCOPE_NAMES = YES
-
-# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
-# will put a list of the files that are included by a file in the documentation
-# of that file.
-
-SHOW_INCLUDE_FILES = YES
-
-# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
-# will list include files with double quotes in the documentation
-# rather than with sharp brackets.
-
-FORCE_LOCAL_INCLUDES = NO
-
-# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
-# is inserted in the documentation for inline members.
-
-INLINE_INFO = YES
-
-# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
-# will sort the (detailed) documentation of file and class members
-# alphabetically by member name. If set to NO the members will appear in
-# declaration order.
-
-SORT_MEMBER_DOCS = YES
-
-# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
-# brief documentation of file, namespace and class members alphabetically
-# by member name. If set to NO (the default) the members will appear in
-# declaration order.
-
-SORT_BRIEF_DOCS = NO
-
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
-# will sort the (brief and detailed) documentation of class members so that
-# constructors and destructors are listed first. If set to NO (the default)
-# the constructors will appear in the respective orders defined by
-# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
-# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
-# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
-
-SORT_MEMBERS_CTORS_1ST = NO
-
-# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
-# hierarchy of group names into alphabetical order. If set to NO (the default)
-# the group names will appear in their defined order.
-
-SORT_GROUP_NAMES = NO
-
-# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
-# sorted by fully-qualified names, including namespaces. If set to
-# NO (the default), the class list will be sorted only by class name,
-# not including the namespace part.
-# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
-# Note: This option applies only to the class list, not to the
-# alphabetical list.
-
-SORT_BY_SCOPE_NAME = NO
-
-# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
-# do proper type resolution of all parameters of a function it will reject a
-# match between the prototype and the implementation of a member function even
-# if there is only one candidate or it is obvious which candidate to choose
-# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
-# will still accept a match between prototype and implementation in such cases.
-
-STRICT_PROTO_MATCHING = NO
-
-# The GENERATE_TODOLIST tag can be used to enable (YES) or
-# disable (NO) the todo list. This list is created by putting \todo
-# commands in the documentation.
-
-GENERATE_TODOLIST = YES
-
-# The GENERATE_TESTLIST tag can be used to enable (YES) or
-# disable (NO) the test list. This list is created by putting \test
-# commands in the documentation.
-
-GENERATE_TESTLIST = YES
-
-# The GENERATE_BUGLIST tag can be used to enable (YES) or
-# disable (NO) the bug list. This list is created by putting \bug
-# commands in the documentation.
-
-GENERATE_BUGLIST = YES
-
-# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
-# disable (NO) the deprecated list. This list is created by putting
-# \deprecated commands in the documentation.
-
-GENERATE_DEPRECATEDLIST= YES
-
-# The ENABLED_SECTIONS tag can be used to enable conditional
-# documentation sections, marked by \if section-label ... \endif
-# and \cond section-label ... \endcond blocks.
-
-ENABLED_SECTIONS =
-
-# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or macro consists of for it to appear in
-# the documentation. If the initializer consists of more lines than specified
-# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and macros in the
-# documentation can be controlled using \showinitializer or \hideinitializer
-# command in the documentation regardless of this setting.
-
-MAX_INITIALIZER_LINES = 30
-
-# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
-# at the bottom of the documentation of classes and structs. If set to YES the
-# list will mention the files that were used to generate the documentation.
-
-SHOW_USED_FILES = YES
-
-# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
-# This will remove the Files entry from the Quick Index and from the
-# Folder Tree View (if specified). The default is YES.
-
-SHOW_FILES = YES
-
-# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
-# Namespaces page. This will remove the Namespaces entry from the Quick Index
-# and from the Folder Tree View (if specified). The default is YES.
-
-SHOW_NAMESPACES = YES
-
-# The FILE_VERSION_FILTER tag can be used to specify a program or script that
-# doxygen should invoke to get the current version for each file (typically from
-# the version control system). Doxygen will invoke the program by executing (via
-# popen()) the command <command> <input-file>, where <command> is the value of
-# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
-# provided by doxygen. Whatever the program writes to standard output
-# is used as the file version. See the manual for examples.
-
-FILE_VERSION_FILTER =
-
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
-# by doxygen. The layout file controls the global structure of the generated
-# output files in an output format independent way. To create the layout file
-# that represents doxygen's defaults, run doxygen with the -l option.
-# You can optionally specify a file name after the option, if omitted
-# DoxygenLayout.xml will be used as the name of the layout file.
-
-LAYOUT_FILE =
-
-# The CITE_BIB_FILES tag can be used to specify one or more bib files
-# containing the references data. This must be a list of .bib files. The
-# .bib extension is automatically appended if omitted. Using this command
-# requires the bibtex tool to be installed. See also
-# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
-# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
-# feature you need bibtex and perl available in the search path. Do not use
-# file names with spaces, bibtex cannot handle them.
-
-CITE_BIB_FILES =
-
-#---------------------------------------------------------------------------
-# configuration options related to warning and progress messages
-#---------------------------------------------------------------------------
-
-# The QUIET tag can be used to turn on/off the messages that are generated
-# by doxygen. Possible values are YES and NO. If left blank NO is used.
-
-QUIET = NO
-
-# The WARNINGS tag can be used to turn on/off the warning messages that are
-# generated by doxygen. Possible values are YES and NO. If left blank
-# NO is used.
-
-WARNINGS = YES
-
-# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
-# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
-# automatically be disabled.
-
-WARN_IF_UNDOCUMENTED = YES
-
-# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
-# potential errors in the documentation, such as not documenting some
-# parameters in a documented function, or documenting parameters that
-# don't exist or using markup commands wrongly.
-
-WARN_IF_DOC_ERROR = YES
-
-# The WARN_NO_PARAMDOC option can be enabled to get warnings for
-# functions that are documented, but have no documentation for their parameters
-# or return value. If set to NO (the default) doxygen will only warn about
-# wrong or incomplete parameter documentation, but not about the absence of
-# documentation.
-
-WARN_NO_PARAMDOC = NO
-
-# The WARN_FORMAT tag determines the format of the warning messages that
-# doxygen can produce. The string should contain the $file, $line, and $text
-# tags, which will be replaced by the file and line number from which the
-# warning originated and the warning text. Optionally the format may contain
-# $version, which will be replaced by the version of the file (if it could
-# be obtained via FILE_VERSION_FILTER)
-
-WARN_FORMAT = "$file:$line: $text"
-
-# The WARN_LOGFILE tag can be used to specify a file to which warning
-# and error messages should be written. If left blank the output is written
-# to stderr.
-
-WARN_LOGFILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the input files
-#---------------------------------------------------------------------------
-
-# The INPUT tag can be used to specify the files and/or directories that contain
-# documented source files. You may enter file names like "myfile.cpp" or
-# directories like "/usr/src/myproject". Separate the files or directories
-# with spaces.
-
-INPUT = ../include/ FRONT.md
-
-# This tag can be used to specify the character encoding of the source files
-# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
-# also the default input encoding. Doxygen uses libiconv (or the iconv built
-# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
-# the list of possible encodings.
-
-INPUT_ENCODING = UTF-8
-
-# If the value of the INPUT tag contains directories, you can use the
-# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
-# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
-# *.f90 *.f *.for *.vhd *.vhdl
-
-FILE_PATTERNS = *.c \
- *.cc \
- *.cxx \
- *.cpp \
- *.c++ \
- *.d \
- *.java \
- *.ii \
- *.ixx \
- *.ipp \
- *.i++ \
- *.inl \
- *.h \
- *.hh \
- *.hxx \
- *.hpp \
- *.h++ \
- *.idl \
- *.odl \
- *.cs \
- *.php \
- *.php3 \
- *.inc \
- *.m \
- *.markdown \
- *.md \
- *.mm \
- *.dox \
- *.py \
- *.f90 \
- *.f \
- *.for \
- *.vhd \
- *.vhdl
-
-# The RECURSIVE tag can be used to turn specify whether or not subdirectories
-# should be searched for input files as well. Possible values are YES and NO.
-# If left blank NO is used.
-
-RECURSIVE = YES
-
-# The EXCLUDE tag can be used to specify files and/or directories that should be
-# excluded from the INPUT source files. This way you can easily exclude a
-# subdirectory from a directory tree whose root is specified with the INPUT tag.
-# Note that relative paths are relative to the directory from which doxygen is
-# run.
-
-EXCLUDE =
-
-# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
-# directories that are symbolic links (a Unix file system feature) are excluded
-# from the input.
-
-EXCLUDE_SYMLINKS = NO
-
-# If the value of the INPUT tag contains directories, you can use the
-# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
-# certain files from those directories. Note that the wildcards are matched
-# against the file with absolute path, so to exclude all test directories
-# for example use the pattern */test/*
-
-EXCLUDE_PATTERNS = */internal/*
-
-# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
-# (namespaces, classes, functions, etc.) that should be excluded from the
-# output. The symbol name can be a fully qualified name, a word, or if the
-# wildcard * is used, a substring. Examples: ANamespace, AClass,
-# AClass::ANamespace, ANamespace::*Test
-
-EXCLUDE_SYMBOLS =
-
-# The EXAMPLE_PATH tag can be used to specify one or more files or
-# directories that contain example code fragments that are included (see
-# the \include command).
-
-EXAMPLE_PATH =
-
-# If the value of the EXAMPLE_PATH tag contains directories, you can use the
-# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
-# and *.h) to filter out the source-files in the directories. If left
-# blank all files are included.
-
-EXAMPLE_PATTERNS = *
-
-# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
-# searched for input files to be used with the \include or \dontinclude
-# commands irrespective of the value of the RECURSIVE tag.
-# Possible values are YES and NO. If left blank NO is used.
-
-EXAMPLE_RECURSIVE = NO
-
-# The IMAGE_PATH tag can be used to specify one or more files or
-# directories that contain image that are included in the documentation (see
-# the \image command).
-
-IMAGE_PATH =
-
-# The INPUT_FILTER tag can be used to specify a program that doxygen should
-# invoke to filter for each input file. Doxygen will invoke the filter program
-# by executing (via popen()) the command <filter> <input-file>, where <filter>
-# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
-# input file. Doxygen will then use the output that the filter program writes
-# to standard output. If FILTER_PATTERNS is specified, this tag will be
-# ignored.
-
-INPUT_FILTER =
-
-# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
-# basis. Doxygen will compare the file name with each pattern and apply the
-# filter if there is a match. The filters are a list of the form:
-# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty or if
-# non of the patterns match the file name, INPUT_FILTER is applied.
-
-FILTER_PATTERNS =
-
-# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
-# INPUT_FILTER) will be used to filter the input files when producing source
-# files to browse (i.e. when SOURCE_BROWSER is set to YES).
-
-FILTER_SOURCE_FILES = NO
-
-# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
-# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
-# and it is also possible to disable source filtering for a specific pattern
-# using *.ext= (so without naming a filter). This option only has effect when
-# FILTER_SOURCE_FILES is enabled.
-
-FILTER_SOURCE_PATTERNS =
-
-# If the USE_MD_FILE_AS_MAINPAGE tag refers to the name of a markdown file that
-# is part of the input, its contents will be placed on the main page (index.html).
-# This can be useful if you have a project on for instance GitHub and want reuse
-# the introduction page also for the doxygen output.
-
-USE_MDFILE_AS_MAINPAGE = FRONT.md
-
-#---------------------------------------------------------------------------
-# configuration options related to source browsing
-#---------------------------------------------------------------------------
-
-# If the SOURCE_BROWSER tag is set to YES then a list of source files will
-# be generated. Documented entities will be cross-referenced with these sources.
-# Note: To get rid of all source code in the generated output, make sure also
-# VERBATIM_HEADERS is set to NO.
-
-SOURCE_BROWSER = NO
-
-# Setting the INLINE_SOURCES tag to YES will include the body
-# of functions and classes directly in the documentation.
-
-INLINE_SOURCES = NO
-
-# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
-# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C, C++ and Fortran comments will always remain visible.
-
-STRIP_CODE_COMMENTS = YES
-
-# If the REFERENCED_BY_RELATION tag is set to YES
-# then for each documented function all documented
-# functions referencing it will be listed.
-
-REFERENCED_BY_RELATION = NO
-
-# If the REFERENCES_RELATION tag is set to YES
-# then for each documented function all documented entities
-# called/used by that function will be listed.
-
-REFERENCES_RELATION = NO
-
-# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
-# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
-# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
-# link to the source code. Otherwise they will link to the documentation.
-
-REFERENCES_LINK_SOURCE = YES
-
-# If the USE_HTAGS tag is set to YES then the references to source code
-# will point to the HTML generated by the htags(1) tool instead of doxygen
-# built-in source browser. The htags tool is part of GNU's global source
-# tagging system (see http://www.gnu.org/software/global/global.html). You
-# will need version 4.8.6 or higher.
-
-USE_HTAGS = NO
-
-# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
-# will generate a verbatim copy of the header file for each class for
-# which an include is specified. Set to NO to disable this.
-
-VERBATIM_HEADERS = YES
-
-#---------------------------------------------------------------------------
-# configuration options related to the alphabetical class index
-#---------------------------------------------------------------------------
-
-# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
-# of all compounds will be generated. Enable this if the project
-# contains a lot of classes, structs, unions or interfaces.
-
-ALPHABETICAL_INDEX = YES
-
-# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
-# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
-# in which this list will be split (can be a number in the range [1..20])
-
-COLS_IN_ALPHA_INDEX = 5
-
-# In case all classes in a project start with a common prefix, all
-# classes will be put under the same header in the alphabetical index.
-# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
-# should be ignored while generating the index headers.
-
-IGNORE_PREFIX =
-
-#---------------------------------------------------------------------------
-# configuration options related to the HTML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
-# generate HTML output.
-
-GENERATE_HTML = YES
-
-# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `html' will be used as the default path.
-
-HTML_OUTPUT = html
-
-# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
-# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
-# doxygen will generate files with .html extension.
-
-HTML_FILE_EXTENSION = .html
-
-# The HTML_HEADER tag can be used to specify a personal HTML header for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard header. Note that when using a custom header you are responsible
-# for the proper inclusion of any scripts and style sheets that doxygen
-# needs, which is dependent on the configuration options used.
-# It is advised to generate a default header using "doxygen -w html
-# header.html footer.html stylesheet.css YourConfigFile" and then modify
-# that header. Note that the header is subject to change so you typically
-# have to redo this when upgrading to a newer version of doxygen or when
-# changing the value of configuration settings such as GENERATE_TREEVIEW!
-
-HTML_HEADER =
-
-# The HTML_FOOTER tag can be used to specify a personal HTML footer for
-# each generated HTML page. If it is left blank doxygen will generate a
-# standard footer.
-
-HTML_FOOTER =
-
-# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
-# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If left blank doxygen will
-# generate a default style sheet. Note that it is recommended to use
-# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this
-# tag will in the future become obsolete.
-
-HTML_STYLESHEET =
-
-# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional
-# user-defined cascading style sheet that is included after the standard
-# style sheets created by doxygen. Using this option one can overrule
-# certain style aspects. This is preferred over using HTML_STYLESHEET
-# since it does not replace the standard style sheet and is therefor more
-# robust against future updates. Doxygen will copy the style sheet file to
-# the output directory.
-
-HTML_EXTRA_STYLESHEET =
-
-# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
-# other source files which should be copied to the HTML output directory. Note
-# that these files will be copied to the base HTML output directory. Use the
-# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
-# files. In the HTML_STYLESHEET file, use the file name only. Also note that
-# the files will be copied as-is; there are no commands or markers available.
-
-HTML_EXTRA_FILES =
-
-# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
-# Doxygen will adjust the colors in the style sheet and background images
-# according to this color. Hue is specified as an angle on a colorwheel,
-# see http://en.wikipedia.org/wiki/Hue for more information.
-# For instance the value 0 represents red, 60 is yellow, 120 is green,
-# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
-# The allowed range is 0 to 359.
-
-HTML_COLORSTYLE_HUE = 220
-
-# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
-# the colors in the HTML output. For a value of 0 the output will use
-# grayscales only. A value of 255 will produce the most vivid colors.
-
-HTML_COLORSTYLE_SAT = 100
-
-# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
-# the luminance component of the colors in the HTML output. Values below
-# 100 gradually make the output lighter, whereas values above 100 make
-# the output darker. The value divided by 100 is the actual gamma applied,
-# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
-# and 100 does not change the gamma.
-
-HTML_COLORSTYLE_GAMMA = 80
-
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting
-# this to NO can help when comparing the output of multiple runs.
-
-HTML_TIMESTAMP = YES
-
-# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
-# documentation will contain sections that can be hidden and shown after the
-# page has loaded.
-
-HTML_DYNAMIC_SECTIONS = NO
-
-# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
-# entries shown in the various tree structured indices initially; the user
-# can expand and collapse entries dynamically later on. Doxygen will expand
-# the tree to such a level that at most the specified number of entries are
-# visible (unless a fully collapsed tree already exceeds this amount).
-# So setting the number of entries 1 will produce a full collapsed tree by
-# default. 0 is a special value representing an infinite number of entries
-# and will result in a full expanded tree by default.
-
-HTML_INDEX_NUM_ENTRIES = 100
-
-# If the GENERATE_DOCSET tag is set to YES, additional index files
-# will be generated that can be used as input for Apple's Xcode 3
-# integrated development environment, introduced with OSX 10.5 (Leopard).
-# To create a documentation set, doxygen will generate a Makefile in the
-# HTML output directory. Running make will produce the docset in that
-# directory and running "make install" will install the docset in
-# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
-# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
-# for more information.
-
-GENERATE_DOCSET = NO
-
-# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
-# feed. A documentation feed provides an umbrella under which multiple
-# documentation sets from a single provider (such as a company or product suite)
-# can be grouped.
-
-DOCSET_FEEDNAME = "Doxygen generated docs"
-
-# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
-# should uniquely identify the documentation set bundle. This should be a
-# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
-# will append .docset to the name.
-
-DOCSET_BUNDLE_ID = org.doxygen.Project
-
-# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely
-# identify the documentation publisher. This should be a reverse domain-name
-# style string, e.g. com.mycompany.MyDocSet.documentation.
-
-DOCSET_PUBLISHER_ID = org.doxygen.Publisher
-
-# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
-
-DOCSET_PUBLISHER_NAME = Publisher
-
-# If the GENERATE_HTMLHELP tag is set to YES, additional index files
-# will be generated that can be used as input for tools like the
-# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
-# of the generated HTML documentation.
-
-GENERATE_HTMLHELP = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
-# be used to specify the file name of the resulting .chm file. You
-# can add a path in front of the file if the result should not be
-# written to the html output directory.
-
-CHM_FILE =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
-# be used to specify the location (absolute path including file name) of
-# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
-# the HTML help compiler on the generated index.hhp.
-
-HHC_LOCATION =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
-# controls if a separate .chi index file is generated (YES) or that
-# it should be included in the master .chm file (NO).
-
-GENERATE_CHI = NO
-
-# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
-# is used to encode HtmlHelp index (hhk), content (hhc) and project file
-# content.
-
-CHM_INDEX_ENCODING =
-
-# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
-# controls whether a binary table of contents is generated (YES) or a
-# normal table of contents (NO) in the .chm file.
-
-BINARY_TOC = NO
-
-# The TOC_EXPAND flag can be set to YES to add extra items for group members
-# to the contents of the HTML help documentation and to the tree view.
-
-TOC_EXPAND = NO
-
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
-# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
-# that can be used as input for Qt's qhelpgenerator to generate a
-# Qt Compressed Help (.qch) of the generated HTML documentation.
-
-GENERATE_QHP = NO
-
-# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
-# be used to specify the file name of the resulting .qch file.
-# The path specified is relative to the HTML output folder.
-
-QCH_FILE =
-
-# The QHP_NAMESPACE tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#namespace
-
-QHP_NAMESPACE = org.doxygen.Project
-
-# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
-# Qt Help Project output. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#virtual-folders
-
-QHP_VIRTUAL_FOLDER = doc
-
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
-# add. For more information please see
-# http://doc.trolltech.com/qthelpproject.html#custom-filters
-
-QHP_CUST_FILTER_NAME =
-
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
-# custom filter to add. For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
-# Qt Help Project / Custom Filters</a>.
-
-QHP_CUST_FILTER_ATTRS =
-
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
-# project's
-# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
-# Qt Help Project / Filter Attributes</a>.
-
-QHP_SECT_FILTER_ATTRS =
-
-# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
-# be used to specify the location of Qt's qhelpgenerator.
-# If non-empty doxygen will try to run qhelpgenerator on the generated
-# .qhp file.
-
-QHG_LOCATION =
-
-# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
-# will be generated, which together with the HTML files, form an Eclipse help
-# plugin. To install this plugin and make it available under the help contents
-# menu in Eclipse, the contents of the directory containing the HTML and XML
-# files needs to be copied into the plugins directory of eclipse. The name of
-# the directory within the plugins directory should be the same as
-# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
-# the help appears.
-
-GENERATE_ECLIPSEHELP = NO
-
-# A unique identifier for the eclipse help plugin. When installing the plugin
-# the directory name containing the HTML and XML files should also have
-# this name.
-
-ECLIPSE_DOC_ID = org.doxygen.Project
-
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
-# at top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it. Since the tabs have the same information as the
-# navigation tree you can set this option to NO if you already set
-# GENERATE_TREEVIEW to YES.
-
-DISABLE_INDEX = NO
-
-# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
-# structure should be generated to display hierarchical information.
-# If the tag value is set to YES, a side panel will be generated
-# containing a tree-like index structure (just like the one that
-# is generated for HTML Help). For this to work a browser that supports
-# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
-# Windows users are probably better off using the HTML help feature.
-# Since the tree basically has the same information as the tab index you
-# could consider to set DISABLE_INDEX to NO when enabling this option.
-
-GENERATE_TREEVIEW = NO
-
-# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
-# (range [0,1..20]) that doxygen will group on one line in the generated HTML
-# documentation. Note that a value of 0 will completely suppress the enum
-# values from appearing in the overview section.
-
-ENUM_VALUES_PER_LINE = 4
-
-# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
-# used to set the initial width (in pixels) of the frame in which the tree
-# is shown.
-
-TREEVIEW_WIDTH = 250
-
-# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
-# links to external symbols imported via tag files in a separate window.
-
-EXT_LINKS_IN_WINDOW = NO
-
-# Use this tag to change the font size of Latex formulas included
-# as images in the HTML documentation. The default is 10. Note that
-# when you change the font size after a successful doxygen run you need
-# to manually remove any form_*.png images from the HTML output directory
-# to force them to be regenerated.
-
-FORMULA_FONTSIZE = 10
-
-# Use the FORMULA_TRANPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are
-# not supported properly for IE 6.0, but are supported on all modern browsers.
-# Note that when changing this option you need to delete any form_*.png files
-# in the HTML output before the changes have effect.
-
-FORMULA_TRANSPARENT = YES
-
-# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
-# (see http://www.mathjax.org) which uses client side Javascript for the
-# rendering instead of using prerendered bitmaps. Use this if you do not
-# have LaTeX installed or if you want to formulas look prettier in the HTML
-# output. When enabled you may also need to install MathJax separately and
-# configure the path to it using the MATHJAX_RELPATH option.
-
-USE_MATHJAX = NO
-
-# When MathJax is enabled you can set the default output format to be used for
-# thA MathJax output. Supported types are HTML-CSS, NativeMML (i.e. MathML) and
-# SVG. The default value is HTML-CSS, which is slower, but has the best
-# compatibility.
-
-MATHJAX_FORMAT = HTML-CSS
-
-# When MathJax is enabled you need to specify the location relative to the
-# HTML output directory using the MATHJAX_RELPATH option. The destination
-# directory should contain the MathJax.js script. For instance, if the mathjax
-# directory is located at the same level as the HTML output directory, then
-# MATHJAX_RELPATH should be ../mathjax. The default value points to
-# the MathJax Content Delivery Network so you can quickly see the result without
-# installing MathJax. However, it is strongly recommended to install a local
-# copy of MathJax from http://www.mathjax.org before deployment.
-
-MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
-
-# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
-# names that should be enabled during MathJax rendering.
-
-MATHJAX_EXTENSIONS =
-
-# When the SEARCHENGINE tag is enabled doxygen will generate a search box
-# for the HTML output. The underlying search engine uses javascript
-# and DHTML and should work on any modern browser. Note that when using
-# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
-# (GENERATE_DOCSET) there is already a search function so this one should
-# typically be disabled. For large projects the javascript based search engine
-# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
-
-SEARCHENGINE = YES
-
-# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
-# implemented using a web server instead of a web client using Javascript.
-# There are two flavours of web server based search depending on the
-# EXTERNAL_SEARCH setting. When disabled, doxygen will generate a PHP script for
-# searching and an index file used by the script. When EXTERNAL_SEARCH is
-# enabled the indexing and searching needs to be provided by external tools.
-# See the manual for details.
-
-SERVER_BASED_SEARCH = NO
-
-# When EXTERNAL_SEARCH is enabled doxygen will no longer generate the PHP
-# script for searching. Instead the search results are written to an XML file
-# which needs to be processed by an external indexer. Doxygen will invoke an
-# external search engine pointed to by the SEARCHENGINE_URL option to obtain
-# the search results. Doxygen ships with an example indexer (doxyindexer) and
-# search engine (doxysearch.cgi) which are based on the open source search engine
-# library Xapian. See the manual for configuration details.
-
-EXTERNAL_SEARCH = NO
-
-# The SEARCHENGINE_URL should point to a search engine hosted by a web server
-# which will returned the search results when EXTERNAL_SEARCH is enabled.
-# Doxygen ships with an example search engine (doxysearch) which is based on
-# the open source search engine library Xapian. See the manual for configuration
-# details.
-
-SEARCHENGINE_URL =
-
-# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
-# search data is written to a file for indexing by an external tool. With the
-# SEARCHDATA_FILE tag the name of this file can be specified.
-
-SEARCHDATA_FILE = searchdata.xml
-
-# When SERVER_BASED_SEARCH AND EXTERNAL_SEARCH are both enabled the
-# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
-# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
-# projects and redirect the results back to the right project.
-
-EXTERNAL_SEARCH_ID =
-
-# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
-# projects other than the one defined by this configuration file, but that are
-# all added to the same external search index. Each project needs to have a
-# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id
-# of to a relative location where the documentation can be found.
-# The format is: EXTRA_SEARCH_MAPPINGS = id1=loc1 id2=loc2 ...
-
-EXTRA_SEARCH_MAPPINGS =
-
-#---------------------------------------------------------------------------
-# configuration options related to the LaTeX output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
-# generate Latex output.
-
-GENERATE_LATEX = NO
-
-# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `latex' will be used as the default path.
-
-LATEX_OUTPUT = latex
-
-# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
-# invoked. If left blank `latex' will be used as the default command name.
-# Note that when enabling USE_PDFLATEX this option is only used for
-# generating bitmaps for formulas in the HTML output, but not in the
-# Makefile that is written to the output directory.
-
-LATEX_CMD_NAME = latex
-
-# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
-# generate index for LaTeX. If left blank `makeindex' will be used as the
-# default command name.
-
-MAKEINDEX_CMD_NAME = makeindex
-
-# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
-# LaTeX documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_LATEX = NO
-
-# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, letter, legal and
-# executive. If left blank a4wide will be used.
-
-PAPER_TYPE = a4
-
-# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
-# packages that should be included in the LaTeX output.
-
-EXTRA_PACKAGES =
-
-# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
-# the generated latex document. The header should contain everything until
-# the first chapter. If it is left blank doxygen will generate a
-# standard header. Notice: only use this tag if you know what you are doing!
-
-LATEX_HEADER =
-
-# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
-# the generated latex document. The footer should contain everything after
-# the last chapter. If it is left blank doxygen will generate a
-# standard footer. Notice: only use this tag if you know what you are doing!
-
-LATEX_FOOTER =
-
-# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
-# is prepared for conversion to pdf (using ps2pdf). The pdf file will
-# contain links (just like the HTML output) instead of page references
-# This makes the output suitable for online browsing using a pdf viewer.
-
-PDF_HYPERLINKS = YES
-
-# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
-# plain latex in the generated Makefile. Set this option to YES to get a
-# higher quality PDF documentation.
-
-USE_PDFLATEX = YES
-
-# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
-# command to the generated LaTeX files. This will instruct LaTeX to keep
-# running if errors occur, instead of asking the user for help.
-# This option is also used when generating formulas in HTML.
-
-LATEX_BATCHMODE = NO
-
-# If LATEX_HIDE_INDICES is set to YES then doxygen will not
-# include the index chapters (such as File Index, Compound Index, etc.)
-# in the output.
-
-LATEX_HIDE_INDICES = NO
-
-# If LATEX_SOURCE_CODE is set to YES then doxygen will include
-# source code with syntax highlighting in the LaTeX output.
-# Note that which sources are shown also depends on other settings
-# such as SOURCE_BROWSER.
-
-LATEX_SOURCE_CODE = NO
-
-# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
-# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
-# http://en.wikipedia.org/wiki/BibTeX for more info.
-
-LATEX_BIB_STYLE = plain
-
-#---------------------------------------------------------------------------
-# configuration options related to the RTF output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
-# The RTF output is optimized for Word 97 and may not look very pretty with
-# other RTF readers or editors.
-
-GENERATE_RTF = NO
-
-# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `rtf' will be used as the default path.
-
-RTF_OUTPUT = rtf
-
-# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
-# RTF documents. This may be useful for small projects and may help to
-# save some trees in general.
-
-COMPACT_RTF = NO
-
-# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
-# will contain hyperlink fields. The RTF file will
-# contain links (just like the HTML output) instead of page references.
-# This makes the output suitable for online browsing using WORD or other
-# programs which support those fields.
-# Note: wordpad (write) and others do not support links.
-
-RTF_HYPERLINKS = NO
-
-# Load style sheet definitions from file. Syntax is similar to doxygen's
-# config file, i.e. a series of assignments. You only have to provide
-# replacements, missing definitions are set to their default value.
-
-RTF_STYLESHEET_FILE =
-
-# Set optional variables used in the generation of an rtf document.
-# Syntax is similar to doxygen's config file.
-
-RTF_EXTENSIONS_FILE =
-
-#---------------------------------------------------------------------------
-# configuration options related to the man page output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
-# generate man pages
-
-GENERATE_MAN = NO
-
-# The MAN_OUTPUT tag is used to specify where the man pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `man' will be used as the default path.
-
-MAN_OUTPUT = man
-
-# The MAN_EXTENSION tag determines the extension that is added to
-# the generated man pages (default is the subroutine's section .3)
-
-MAN_EXTENSION = .3
-
-# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
-# then it will generate one additional man file for each entity
-# documented in the real man page(s). These additional files
-# only source the real man page, but without them the man command
-# would be unable to find the correct page. The default is NO.
-
-MAN_LINKS = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the XML output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_XML tag is set to YES Doxygen will
-# generate an XML file that captures the structure of
-# the code including all documentation.
-
-GENERATE_XML = NO
-
-# The XML_OUTPUT tag is used to specify where the XML pages will be put.
-# If a relative path is entered the value of OUTPUT_DIRECTORY will be
-# put in front of it. If left blank `xml' will be used as the default path.
-
-XML_OUTPUT = xml
-
-# The XML_SCHEMA tag can be used to specify an XML schema,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_SCHEMA =
-
-# The XML_DTD tag can be used to specify an XML DTD,
-# which can be used by a validating XML parser to check the
-# syntax of the XML files.
-
-XML_DTD =
-
-# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
-# dump the program listings (including syntax highlighting
-# and cross-referencing information) to the XML output. Note that
-# enabling this will significantly increase the size of the XML output.
-
-XML_PROGRAMLISTING = YES
-
-#---------------------------------------------------------------------------
-# configuration options for the AutoGen Definitions output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
-# generate an AutoGen Definitions (see autogen.sf.net) file
-# that captures the structure of the code including all
-# documentation. Note that this feature is still experimental
-# and incomplete at the moment.
-
-GENERATE_AUTOGEN_DEF = NO
-
-#---------------------------------------------------------------------------
-# configuration options related to the Perl module output
-#---------------------------------------------------------------------------
-
-# If the GENERATE_PERLMOD tag is set to YES Doxygen will
-# generate a Perl module file that captures the structure of
-# the code including all documentation. Note that this
-# feature is still experimental and incomplete at the
-# moment.
-
-GENERATE_PERLMOD = NO
-
-# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
-# the necessary Makefile rules, Perl scripts and LaTeX code to be able
-# to generate PDF and DVI output from the Perl module output.
-
-PERLMOD_LATEX = NO
-
-# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
-# nicely formatted so it can be parsed by a human reader. This is useful
-# if you want to understand what is going on. On the other hand, if this
-# tag is set to NO the size of the Perl module output will be much smaller
-# and Perl will parse it just the same.
-
-PERLMOD_PRETTY = YES
-
-# The names of the make variables in the generated doxyrules.make file
-# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
-# This is useful so different doxyrules.make files included by the same
-# Makefile don't overwrite each other's variables.
-
-PERLMOD_MAKEVAR_PREFIX =
-
-#---------------------------------------------------------------------------
-# Configuration options related to the preprocessor
-#---------------------------------------------------------------------------
-
-# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
-# evaluate all C-preprocessor directives found in the sources and include
-# files.
-
-ENABLE_PREPROCESSING = YES
-
-# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
-# names in the source code. If set to NO (the default) only conditional
-# compilation will be performed. Macro expansion can be done in a controlled
-# way by setting EXPAND_ONLY_PREDEF to YES.
-
-MACRO_EXPANSION = NO
-
-# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
-# then the macro expansion is limited to the macros specified with the
-# PREDEFINED and EXPAND_AS_DEFINED tags.
-
-EXPAND_ONLY_PREDEF = NO
-
-# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# pointed to by INCLUDE_PATH will be searched when a #include is found.
-
-SEARCH_INCLUDES = YES
-
-# The INCLUDE_PATH tag can be used to specify one or more directories that
-# contain include files that are not input files but should be processed by
-# the preprocessor.
-
-INCLUDE_PATH =
-
-# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
-# patterns (like *.h and *.hpp) to filter out the header-files in the
-# directories. If left blank, the patterns specified with FILE_PATTERNS will
-# be used.
-
-INCLUDE_FILE_PATTERNS =
-
-# The PREDEFINED tag can be used to specify one or more macro names that
-# are defined before the preprocessor is started (similar to the -D option of
-# gcc). The argument of the tag is a list of macros of the form: name
-# or name=definition (no spaces). If the definition and the = are
-# omitted =1 is assumed. To prevent a macro definition from being
-# undefined via #undef or recursively expanded use the := operator
-# instead of the = operator.
-
-PREDEFINED =
-
-# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
-# this tag can be used to specify a list of macro names that should be expanded.
-# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition that
-# overrules the definition found in the source code.
-
-EXPAND_AS_DEFINED =
-
-# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all references to function-like macros
-# that are alone on a line, have an all uppercase name, and do not end with a
-# semicolon, because these will confuse the parser if not removed.
-
-SKIP_FUNCTION_MACROS = YES
-
-#---------------------------------------------------------------------------
-# Configuration::additions related to external references
-#---------------------------------------------------------------------------
-
-# The TAGFILES option can be used to specify one or more tagfiles. For each
-# tag file the location of the external documentation should be added. The
-# format of a tag file without this location is as follows:
-# TAGFILES = file1 file2 ...
-# Adding location for the tag files is done as follows:
-# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths
-# or URLs. Note that each tag file must have a unique name (where the name does
-# NOT include the path). If a tag file is not located in the directory in which
-# doxygen is run, you must also specify the path to the tagfile here.
-
-TAGFILES =
-
-# When a file name is specified after GENERATE_TAGFILE, doxygen will create
-# a tag file that is based on the input files it reads.
-
-GENERATE_TAGFILE =
-
-# If the ALLEXTERNALS tag is set to YES all external classes will be listed
-# in the class index. If set to NO only the inherited external classes
-# will be listed.
-
-ALLEXTERNALS = NO
-
-# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
-# in the modules index. If set to NO, only the current project's groups will
-# be listed.
-
-EXTERNAL_GROUPS = YES
-
-# The PERL_PATH should be the absolute path and name of the perl script
-# interpreter (i.e. the result of `which perl').
-
-PERL_PATH = /usr/bin/perl
-
-#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
-#---------------------------------------------------------------------------
-
-# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
-# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
-# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option also works with HAVE_DOT disabled, but it is recommended to
-# install and use dot, since it yields more powerful graphs.
-
-CLASS_DIAGRAMS = YES
-
-# You can define message sequence charts within doxygen comments using the \msc
-# command. Doxygen will then run the mscgen tool (see
-# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
-# documentation. The MSCGEN_PATH tag allows you to specify the directory where
-# the mscgen tool resides. If left empty the tool is assumed to be found in the
-# default search path.
-
-MSCGEN_PATH =
-
-# If set to YES, the inheritance and collaboration graphs will hide
-# inheritance and usage relations if the target is undocumented
-# or is not a class.
-
-HIDE_UNDOC_RELATIONS = YES
-
-# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
-# available from the path. This tool is part of Graphviz, a graph visualization
-# toolkit from AT&T and Lucent Bell Labs. The other options in this section
-# have no effect if this option is set to NO (the default)
-
-HAVE_DOT = NO
-
-# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
-# allowed to run in parallel. When set to 0 (the default) doxygen will
-# base this on the number of processors available in the system. You can set it
-# explicitly to a value larger than 0 to get control over the balance
-# between CPU load and processing speed.
-
-DOT_NUM_THREADS = 0
-
-# By default doxygen will use the Helvetica font for all dot files that
-# doxygen generates. When you want a differently looking font you can specify
-# the font name using DOT_FONTNAME. You need to make sure dot is able to find
-# the font, which can be done by putting it in a standard location or by setting
-# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
-# directory containing the font.
-
-DOT_FONTNAME = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
-# The default size is 10pt.
-
-DOT_FONTSIZE = 10
-
-# By default doxygen will tell dot to use the Helvetica font.
-# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
-# set the path where dot can find it.
-
-DOT_FONTPATH =
-
-# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect inheritance relations. Setting this tag to YES will force the
-# CLASS_DIAGRAMS tag to NO.
-
-CLASS_GRAPH = YES
-
-# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for each documented class showing the direct and
-# indirect implementation dependencies (inheritance, containment, and
-# class references variables) of the class with other documented classes.
-
-COLLABORATION_GRAPH = YES
-
-# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
-# will generate a graph for groups, showing the direct groups dependencies
-
-GROUP_GRAPHS = YES
-
-# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
-# collaboration diagrams in a style similar to the OMG's Unified Modeling
-# Language.
-
-UML_LOOK = NO
-
-# If the UML_LOOK tag is enabled, the fields and methods are shown inside
-# the class node. If there are many fields or methods and many nodes the
-# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
-# threshold limits the number of items for each type to make the size more
-# managable. Set this to 0 for no limit. Note that the threshold may be
-# exceeded by 50% before the limit is enforced.
-
-UML_LIMIT_NUM_FIELDS = 10
-
-# If set to YES, the inheritance and collaboration graphs will show the
-# relations between templates and their instances.
-
-TEMPLATE_RELATIONS = NO
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
-# tags are set to YES then doxygen will generate a graph for each documented
-# file showing the direct and indirect include dependencies of the file with
-# other documented files.
-
-INCLUDE_GRAPH = YES
-
-# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
-# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
-# documented header file showing the documented files that directly or
-# indirectly include this file.
-
-INCLUDED_BY_GRAPH = YES
-
-# If the CALL_GRAPH and HAVE_DOT options are set to YES then
-# doxygen will generate a call dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable call graphs
-# for selected functions only using the \callgraph command.
-
-CALL_GRAPH = NO
-
-# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
-# doxygen will generate a caller dependency graph for every global function
-# or class method. Note that enabling this option will significantly increase
-# the time of a run. So in most cases it will be better to enable caller
-# graphs for selected functions only using the \callergraph command.
-
-CALLER_GRAPH = NO
-
-# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will generate a graphical hierarchy of all classes instead of a textual one.
-
-GRAPHICAL_HIERARCHY = YES
-
-# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
-# then doxygen will show the dependencies a directory has on other directories
-# in a graphical way. The dependency relations are determined by the #include
-# relations between the files in the directories.
-
-DIRECTORY_GRAPH = YES
-
-# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are svg, png, jpg, or gif.
-# If left blank png will be used. If you choose svg you need to set
-# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible in IE 9+ (other browsers do not have this requirement).
-
-DOT_IMAGE_FORMAT = png
-
-# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
-# enable generation of interactive SVG images that allow zooming and panning.
-# Note that this requires a modern browser other than Internet Explorer.
-# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
-# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
-# visible. Older versions of IE do not have SVG support.
-
-INTERACTIVE_SVG = NO
-
-# The tag DOT_PATH can be used to specify the path where the dot tool can be
-# found. If left blank, it is assumed the dot tool can be found in the path.
-
-DOT_PATH =
-
-# The DOTFILE_DIRS tag can be used to specify one or more directories that
-# contain dot files that are included in the documentation (see the
-# \dotfile command).
-
-DOTFILE_DIRS =
-
-# The MSCFILE_DIRS tag can be used to specify one or more directories that
-# contain msc files that are included in the documentation (see the
-# \mscfile command).
-
-MSCFILE_DIRS =
-
-# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
-# nodes that will be shown in the graph. If the number of nodes in a graph
-# becomes larger than this value, doxygen will truncate the graph, which is
-# visualized by representing a node as a red box. Note that doxygen if the
-# number of direct children of the root node in a graph is already larger than
-# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
-# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
-
-DOT_GRAPH_MAX_NODES = 50
-
-# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
-# graphs generated by dot. A depth value of 3 means that only nodes reachable
-# from the root by following a path via at most 3 edges will be shown. Nodes
-# that lay further from the root node will be omitted. Note that setting this
-# option to 1 or 2 may greatly reduce the computation time needed for large
-# code bases. Also note that the size of a graph can be further restricted by
-# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
-
-MAX_DOT_GRAPH_DEPTH = 0
-
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not
-# seem to support this out of the box. Warning: Depending on the platform used,
-# enabling this option may lead to badly anti-aliased labels on the edges of
-# a graph (i.e. they become hard to read).
-
-DOT_TRANSPARENT = NO
-
-# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
-# files in one run (i.e. multiple -o and -T options on the command line). This
-# makes dot run faster, but since only newer versions of dot (>1.8.10)
-# support this, this feature is disabled by default.
-
-DOT_MULTI_TARGETS = NO
-
-# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
-# generate a legend page explaining the meaning of the various boxes and
-# arrows in the dot generated graphs.
-
-GENERATE_LEGEND = YES
-
-# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
-# remove the intermediate dot files that are used to generate
-# the various graphs.
-
-DOT_CLEANUP = YES
diff --git a/doc/FRONT.md b/doc/FRONT.md
deleted file mode 100644
index 3bc6c4c..0000000
--- a/doc/FRONT.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# SDL_kitchensink
-
-## Overview
-
-FFmpeg and SDL2 based library for audio and video playback, written in C99.
-Please see [github.com/katajakasa/SDL_kitchensink](https://github.com/katajakasa/SDL_kitchensink)
-for project details and source code.
-
-Features:
-* Decoding video & audio via FFmpeg
-* Dumping video data on SDL_textures
-* Dumping audio data in the usual mono/stereo interleaved formats
-* Automatic audio and video conversion to SDL2 friendly formats
-* Synchronizing video & audio to clock
-* Seeking forwards and backwards
-* Bitmap & libass subtitle support. No text (srt, sub) support yet.
-
-Note that FFmpeg has a rather complex license. Please take a look at
-[FFmpeg Legal page](http://ffmpeg.org/legal.html) for details.
-
-## License
-
-The MIT License (MIT)
-
-Copyright (c) 2016 Tuomas Virtanen
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/doc/generate.sh b/doc/generate.sh
deleted file mode 100644
index 6307434..0000000
--- a/doc/generate.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-doxygen Doxyfile \ No newline at end of file
diff --git a/doxygen-custom.css b/doxygen-custom.css
new file mode 100644
index 0000000..cb0d91e
--- /dev/null
+++ b/doxygen-custom.css
@@ -0,0 +1,3 @@
+.fragment {
+ padding: 0.5em !important;
+}
diff --git a/examples/example_audio.c b/examples/example_audio.c
index 9780c26..20c8cdf 100644
--- a/examples/example_audio.c
+++ b/examples/example_audio.c
@@ -1,16 +1,14 @@
#include <kitchensink/kitchensink.h>
-#include <SDL2/SDL.h>
+#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
/*
-* Requires SDL2 2.0.4 !
-*
* Note! This example does not do proper error handling etc.
* It is for example use only!
*/
-#define AUDIOBUFFER_SIZE (16384)
+#define AUDIOBUFFER_SIZE (32768)
const char *stream_types[] = {
"KIT_STREAMTYPE_UNKNOWN",
@@ -39,7 +37,7 @@ int main(int argc, char *argv[]) {
// Get filename to open
if(argc != 2) {
- fprintf(stderr, "Usage: exampleplay <filename>\n");
+ fprintf(stderr, "Usage: audio <filename>\n");
return 0;
}
filename = argv[1];
@@ -51,7 +49,7 @@ int main(int argc, char *argv[]) {
return 1;
}
- err = Kit_Init(KIT_INIT_FORMATS|KIT_INIT_NETWORK);
+ err = Kit_Init(KIT_INIT_NETWORK);
if(err != 0) {
fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError());
return 1;
@@ -64,13 +62,8 @@ int main(int argc, char *argv[]) {
return 1;
}
- // Disable any video and subtitle streams. If we leave these enabled and then don't
- // clear the buffers for these sometimes, decoding will block.
- Kit_SetSourceStream(src, KIT_STREAMTYPE_SUBTITLE, -1);
- Kit_SetSourceStream(src, KIT_STREAMTYPE_VIDEO, -1);
-
// Print stream information
- Kit_StreamInfo sinfo;
+ Kit_SourceStreamInfo sinfo;
fprintf(stderr, "Source streams:\n");
for(int i = 0; i < Kit_GetSourceStreamCount(src); i++) {
err = Kit_GetSourceStreamInfo(src, &sinfo, i);
@@ -81,8 +74,13 @@ int main(int argc, char *argv[]) {
fprintf(stderr, " * Stream #%d: %s\n", i, stream_types[sinfo.type]);
}
- // Create the player
- player = Kit_CreatePlayer(src);
+ // Create the player. No video, pick best audio stream, no subtitles, no screen
+ player = Kit_CreatePlayer(
+ src,
+ -1,
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ -1,
+ 0, 0);
if(player == NULL) {
fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
return 1;
@@ -92,25 +90,26 @@ int main(int argc, char *argv[]) {
Kit_PlayerInfo pinfo;
Kit_GetPlayerInfo(player, &pinfo);
- if(!pinfo.audio.is_enabled) {
+ // Make sure there is audio in the file to play first.
+ if(Kit_GetPlayerAudioStream(player) == -1) {
fprintf(stderr, "File contains no audio!\n");
return 1;
}
fprintf(stderr, "Media information:\n");
fprintf(stderr, " * Audio: %s (%s), %dHz, %dch, %db, %s\n",
- pinfo.acodec,
- pinfo.acodec_name,
- pinfo.audio.samplerate,
- pinfo.audio.channels,
- pinfo.audio.bytes,
- pinfo.audio.is_signed ? "signed" : "unsigned");
+ pinfo.audio.codec.name,
+ pinfo.audio.codec.description,
+ pinfo.audio.output.samplerate,
+ pinfo.audio.output.channels,
+ pinfo.audio.output.bytes,
+ pinfo.audio.output.is_signed ? "signed" : "unsigned");
// Init audio
SDL_memset(&wanted_spec, 0, sizeof(wanted_spec));
- wanted_spec.freq = pinfo.audio.samplerate;
- wanted_spec.format = pinfo.audio.format;
- wanted_spec.channels = pinfo.audio.channels;
+ wanted_spec.freq = pinfo.audio.output.samplerate;
+ wanted_spec.format = pinfo.audio.output.format;
+ wanted_spec.channels = pinfo.audio.output.channels;
audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0);
SDL_PauseAudioDevice(audio_dev, 0);
@@ -127,26 +126,22 @@ int main(int argc, char *argv[]) {
}
// Refresh audio
- ret = SDL_GetQueuedAudioSize(audio_dev);
- if(ret < AUDIOBUFFER_SIZE) {
- ret = Kit_GetAudioData(player, (unsigned char*)audiobuf, AUDIOBUFFER_SIZE, 0);
+ int queued = SDL_GetQueuedAudioSize(audio_dev);
+ if(queued < AUDIOBUFFER_SIZE) {
+ ret = Kit_GetPlayerAudioData(player, (unsigned char*)audiobuf, AUDIOBUFFER_SIZE - queued);
if(ret > 0) {
- SDL_LockAudio();
SDL_QueueAudio(audio_dev, audiobuf, ret);
- SDL_UnlockAudio();
- SDL_PauseAudioDevice(audio_dev, 0);
}
}
SDL_Delay(1);
}
- SDL_CloseAudioDevice(audio_dev);
-
Kit_ClosePlayer(player);
Kit_CloseSource(src);
-
Kit_Quit();
+
+ SDL_CloseAudioDevice(audio_dev);
SDL_Quit();
return 0;
}
diff --git a/examples/example_video.c b/examples/example_complex.c
index 1f411ca..b8dc6ee 100644
--- a/examples/example_video.c
+++ b/examples/example_complex.c
@@ -1,16 +1,17 @@
#include <kitchensink/kitchensink.h>
-#include <SDL2/SDL.h>
+#include <SDL.h>
#include <stdio.h>
#include <stdbool.h>
/*
-* Requires SDL2 2.0.4 !
-*
* Note! This example does not do proper error handling etc.
* It is for example use only!
*/
-#define AUDIOBUFFER_SIZE (32768)
+#define AUDIOBUFFER_SIZE (1024 * 64)
+#define ATLAS_WIDTH 4096
+#define ATLAS_HEIGHT 4096
+#define ATLAS_MAX 1024
void render_gui(SDL_Renderer *renderer, double percent) {
@@ -44,6 +45,14 @@ void render_gui(SDL_Renderer *renderer, double percent) {
SDL_RenderFillRect(renderer, &progress_top);
}
+void find_viewport_size(int sw, int sh, int vw, int vh, int *rw, int *rh) {
+ float r_x = (float)sw / (float)vw;
+ float r_y = (float)sh / (float)vh;
+ float r_t = r_x < r_y ? r_x : r_y;
+ *rw = vw * r_t;
+ *rh = vh * r_t;
+}
+
int main(int argc, char *argv[]) {
int err = 0, ret = 0;
const char* filename = NULL;
@@ -63,11 +72,10 @@ int main(int argc, char *argv[]) {
// Audio playback
SDL_AudioSpec wanted_spec, audio_spec;
SDL_AudioDeviceID audio_dev;
- char audiobuf[AUDIOBUFFER_SIZE];
// Get filename to open
if(argc != 2) {
- fprintf(stderr, "Usage: exampleplay <filename>\n");
+ fprintf(stderr, "Usage: complex <filename>\n");
return 0;
}
filename = argv[1];
@@ -79,9 +87,6 @@ int main(int argc, char *argv[]) {
return 1;
}
- // Attempt to acquire opengl driver context
- SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
-
// Create a resizable window.
window = SDL_CreateWindow("Example Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
if(window == NULL) {
@@ -96,22 +101,17 @@ int main(int argc, char *argv[]) {
return 1;
}
- // We want to alphablend textures, so switch that on
- if(SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND) < 0) {
- fprintf(stderr, "Unable to set blendmode!\n");
- return 1;
- }
-
- // Ask for linear texture scaling (better quality)
- SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
-
- // Initialize Kitchensink with network support and all formats.
- err = Kit_Init(KIT_INIT_FORMATS|KIT_INIT_NETWORK);
+ // Initialize Kitchensink with network and libass support.
+ err = Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS);
if(err != 0) {
fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError());
return 1;
}
+ // Allow Kit to use more threads
+ Kit_SetHint(KIT_HINT_THREAD_COUNT, SDL_GetCPUCount() <= 8 ? SDL_GetCPUCount() : 8);
+ Kit_SetHint(KIT_HINT_FONT_HINTING, KIT_FONT_HINTING_LIGHT);
+
// Open up the sourcefile.
// This can be a local file, network url, ...
src = Kit_CreateSourceFromUrl(filename);
@@ -121,7 +121,7 @@ int main(int argc, char *argv[]) {
}
// Print stream information
- Kit_StreamInfo sinfo;
+ Kit_SourceStreamInfo sinfo;
fprintf(stderr, "Source streams:\n");
for(int i = 0; i < Kit_GetSourceStreamCount(src); i++) {
err = Kit_GetSourceStreamInfo(src, &sinfo, i);
@@ -132,8 +132,14 @@ int main(int argc, char *argv[]) {
fprintf(stderr, " * Stream #%d: %s\n", i, Kit_GetKitStreamTypeString(sinfo.type));
}
- // Create the player
- player = Kit_CreatePlayer(src);
+ // Create the player. Pick best video, audio and subtitle streams, and set subtitle
+ // rendering resolution to screen resolution.
+ player = Kit_CreatePlayer(
+ src,
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE),
+ 1280, 720);
if(player == NULL) {
fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
return 1;
@@ -143,71 +149,107 @@ int main(int argc, char *argv[]) {
Kit_PlayerInfo pinfo;
Kit_GetPlayerInfo(player, &pinfo);
- if(!pinfo.video.is_enabled) {
+ // Make sure there is video in the file to play first.
+ if(Kit_GetPlayerVideoStream(player) == -1) {
fprintf(stderr, "File contains no video!\n");
return 1;
}
fprintf(stderr, "Media information:\n");
- if(pinfo.audio.is_enabled) {
- fprintf(stderr, " * Audio: %s (%s), %dHz, %dch, %db, %s\n",
- pinfo.acodec,
- pinfo.acodec_name,
- pinfo.audio.samplerate,
- pinfo.audio.channels,
- pinfo.audio.bytes,
- pinfo.audio.is_signed ? "signed" : "unsigned");
+ if(Kit_GetPlayerAudioStream(player) >= 0) {
+ fprintf(stderr, " * Audio: %s (%s), threads=%d, %dHz, %dch, %db, %s\n",
+ pinfo.audio.codec.name,
+ pinfo.audio.codec.description,
+ pinfo.video.codec.threads,
+ pinfo.audio.output.samplerate,
+ pinfo.audio.output.channels,
+ pinfo.audio.output.bytes,
+ pinfo.audio.output.is_signed ? "signed" : "unsigned");
}
- fprintf(stderr, " * Video: %s (%s), %dx%d\n",
- pinfo.vcodec,
- pinfo.vcodec_name,
- pinfo.video.width,
- pinfo.video.height);
- if(pinfo.subtitle.is_enabled) {
- fprintf(stderr, " * Subtitle: %s (%s)\n",
- pinfo.scodec,
- pinfo.scodec_name);
+ if(Kit_GetPlayerVideoStream(player) >= 0) {
+ fprintf(stderr, " * Video: %s (%s), threads=%d, %dx%d\n",
+ pinfo.video.codec.name,
+ pinfo.video.codec.description,
+ pinfo.video.codec.threads,
+ pinfo.video.output.width,
+ pinfo.video.output.height);
+ }
+ if(Kit_GetPlayerSubtitleStream(player) >= 0) {
+ fprintf(stderr, " * Subtitle: %s (%s), threads=%d\n",
+ pinfo.subtitle.codec.name,
+ pinfo.subtitle.codec.description,
+ pinfo.video.codec.threads);
}
fprintf(stderr, "Duration: %f seconds\n", Kit_GetPlayerDuration(player));
// Init audio
SDL_memset(&wanted_spec, 0, sizeof(wanted_spec));
- wanted_spec.freq = pinfo.audio.samplerate;
- wanted_spec.format = pinfo.audio.format;
- wanted_spec.channels = pinfo.audio.channels;
+ wanted_spec.freq = pinfo.audio.output.samplerate;
+ wanted_spec.format = pinfo.audio.output.format;
+ wanted_spec.channels = pinfo.audio.output.channels;
audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0);
SDL_PauseAudioDevice(audio_dev, 0);
// Print some format info
- fprintf(stderr, "Texture type: %s\n", Kit_GetSDLPixelFormatString(pinfo.video.format));
- fprintf(stderr, "Audio format: %s\n", Kit_GetSDLAudioFormatString(pinfo.audio.format));
+ fprintf(stderr, "Texture type: %s\n", Kit_GetSDLPixelFormatString(pinfo.video.output.format));
+ fprintf(stderr, "Audio format: %s\n", Kit_GetSDLAudioFormatString(pinfo.audio.output.format));
+ fprintf(stderr, "Subtitle format: %s\n", Kit_GetSDLPixelFormatString(pinfo.subtitle.output.format));
+ fflush(stderr);
- // Initialize textures
+ // Initialize video texture. This will probably end up as YV12 most of the time.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
SDL_Texture *video_tex = SDL_CreateTexture(
renderer,
- pinfo.video.format,
+ pinfo.video.output.format,
SDL_TEXTUREACCESS_STATIC,
- pinfo.video.width,
- pinfo.video.height);
+ pinfo.video.output.width,
+ pinfo.video.output.height);
if(video_tex == NULL) {
fprintf(stderr, "Error while attempting to create a video texture\n");
return 1;
}
- fflush(stderr);
+ // This is the subtitle texture atlas. This contains all the subtitle image fragments.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations
+ SDL_Texture *subtitle_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.subtitle.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ ATLAS_WIDTH, ATLAS_HEIGHT);
+ if(subtitle_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n");
+ return 1;
+ }
+
+ // Make sure subtitle texture is in correct blendmode
+ SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND);
- // Set logical size for the renderer. This way when we scale, we keep aspect ratio.
- SDL_RenderSetLogicalSize(renderer, pinfo.video.width, pinfo.video.height);
+ // Clear screen with black
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
// Start playback
Kit_PlayerPlay(player);
- // Get movie area size
- int mouse_x = 0, mouse_y = 0;
- int size_w = 0, size_h = 0;
- SDL_RenderGetLogicalSize(renderer, &size_w, &size_h);
- bool gui_enabled = false;
+ // Playback temporary data buffers
+ char audiobuf[AUDIOBUFFER_SIZE];
+ SDL_Rect sources[ATLAS_MAX];
+ SDL_Rect targets[ATLAS_MAX];
+ int mouse_x = 0;
+ int mouse_y = 0;
+ int size_w = 0;
+ int size_h = 0;
+ int screen_w = 0;
+ int screen_h = 0;
bool fullscreen = false;
+
+ // Get movie area size
+ SDL_GetWindowSize(window, &screen_w, &screen_h);
+ find_viewport_size(screen_w, screen_h, pinfo.video.output.width, pinfo.video.output.height, &size_w, &size_h);
+ SDL_RenderSetLogicalSize(renderer, size_w, size_h);
+ Kit_SetPlayerScreenSize(player, size_w, size_h);
+
+ // Run until playback is stopped
while(run) {
if(Kit_GetPlayerState(player) == KIT_STOPPED) {
run = false;
@@ -222,30 +264,6 @@ int main(int argc, char *argv[]) {
if(event.key.keysym.sym == SDLK_ESCAPE) {
run = false;
}
- if(event.key.keysym.sym == SDLK_q) {
- // Start or unpause the video
- Kit_PlayerPlay(player);
- }
- if(event.key.keysym.sym == SDLK_w) {
- // Pause playback
- Kit_PlayerPause(player);
- }
- if(event.key.keysym.sym == SDLK_e) {
- // Stop playback (will close the window)
- Kit_PlayerStop(player);
- }
- if(event.key.keysym.sym == SDLK_RIGHT) {
- // Skip 10 seconds forwards or to the end of the file
- if(Kit_PlayerSeek(player, 10.0) != 0) {
- fprintf(stderr, "%s\n", Kit_GetError());
- }
- }
- if(event.key.keysym.sym == SDLK_LEFT) {
- // Seek 10 seconds backwards or to the start of the file
- if(Kit_PlayerSeek(player, -10.0) != 0) {
- fprintf(stderr, "%s\n", Kit_GetError());
- }
- }
break;
case SDL_KEYDOWN:
@@ -266,14 +284,27 @@ int main(int argc, char *argv[]) {
mouse_y = event.motion.y;
break;
+ case SDL_WINDOWEVENT:
+ switch(event.window.event) {
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ SDL_GetWindowSize(window, &screen_w, &screen_h);
+ find_viewport_size(
+ screen_w, screen_h, pinfo.video.output.width, pinfo.video.output.height, &size_w, &size_h);
+ SDL_RenderSetLogicalSize(renderer, size_w, size_h);
+ Kit_SetPlayerScreenSize(player, size_w, size_h);
+ break;
+ }
+ break;
+
case SDL_MOUSEBUTTONUP:
// Handle user clicking the progress bar
if(mouse_x >= 30 && mouse_x <= size_w-30 && mouse_y >= size_h - 60 && mouse_y <= size_h - 40) {
double pos = ((double)mouse_x - 30) / ((double)size_w - 60);
- double m_time = Kit_GetPlayerDuration(player) * pos - Kit_GetPlayerPosition(player);
+ double m_time = Kit_GetPlayerDuration(player) * pos;
if(Kit_PlayerSeek(player, m_time) != 0) {
fprintf(stderr, "%s\n", Kit_GetError());
}
+ SDL_ClearQueuedAudio(audio_dev);
} else {
// Handle pause
if(Kit_GetPlayerState(player) == KIT_PAUSED) {
@@ -290,17 +321,16 @@ int main(int argc, char *argv[]) {
}
}
- // Enable GUI if mouse is hovering over the bottom third of the screen
- int limit = (pinfo.video.height / 3) * 2;
- gui_enabled = (mouse_y >= limit);
-
// Refresh audio
- if(SDL_GetQueuedAudioSize(audio_dev) < AUDIOBUFFER_SIZE) {
- int need = AUDIOBUFFER_SIZE - ret;
+ int queued = SDL_GetQueuedAudioSize(audio_dev);
+ if(queued < AUDIOBUFFER_SIZE) {
+ int need = AUDIOBUFFER_SIZE - queued;
- SDL_LockAudio();
while(need > 0) {
- ret = Kit_GetAudioData(player, (unsigned char*)audiobuf, AUDIOBUFFER_SIZE, (size_t)SDL_GetQueuedAudioSize(audio_dev));
+ ret = Kit_GetPlayerAudioData(
+ player,
+ (unsigned char*)audiobuf,
+ AUDIOBUFFER_SIZE);
need -= ret;
if(ret > 0) {
SDL_QueueAudio(audio_dev, audiobuf, ret);
@@ -308,21 +338,25 @@ int main(int argc, char *argv[]) {
break;
}
}
- SDL_UnlockAudio();
- SDL_PauseAudioDevice(audio_dev, 0);
+ // If we now have data, start playback (again)
+ if(SDL_GetQueuedAudioSize(audio_dev) > 0) {
+ SDL_PauseAudioDevice(audio_dev, 0);
+ }
}
- // Clear screen with black
- SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
- SDL_RenderClear(renderer);
-
// Refresh videotexture and render it
- Kit_GetVideoData(player, video_tex);
+ Kit_GetPlayerVideoData(player, video_tex);
SDL_RenderCopy(renderer, video_tex, NULL, NULL);
- Kit_GetSubtitleData(player, renderer);
- // Render GUI
- if(gui_enabled) {
+ // Refresh subtitle texture atlas and render subtitle frames from it
+ // For subtitles, use screen size instead of video size for best quality
+ int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, ATLAS_MAX);
+ for(int i = 0; i < got; i++) {
+ SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]);
+ }
+
+ // Enable GUI if mouse is hovering over the bottom third of the screen
+ if(mouse_y >= ((size_h / 3) * 2)) {
double percent = Kit_GetPlayerPosition(player) / Kit_GetPlayerDuration(player);
render_gui(renderer, percent);
}
@@ -331,13 +365,14 @@ int main(int argc, char *argv[]) {
SDL_RenderPresent(renderer);
}
- SDL_DestroyTexture(video_tex);
- SDL_CloseAudioDevice(audio_dev);
-
Kit_ClosePlayer(player);
Kit_CloseSource(src);
-
Kit_Quit();
+
+ SDL_DestroyTexture(subtitle_tex);
+ SDL_DestroyTexture(video_tex);
+ SDL_CloseAudioDevice(audio_dev);
+
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
diff --git a/examples/example_custom.c b/examples/example_custom.c
new file mode 100644
index 0000000..f99c94e
--- /dev/null
+++ b/examples/example_custom.c
@@ -0,0 +1,230 @@
+#include <kitchensink/kitchensink.h>
+#include <SDL.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+/*
+* Note! This example does not do proper error handling etc.
+* It is for example use only!
+*/
+
+#define AUDIOBUFFER_SIZE (1024 * 64)
+#define ATLAS_WIDTH 4096
+#define ATLAS_HEIGHT 4096
+#define ATLAS_MAX 1024
+
+
+int read_callback(void *userdata, uint8_t *buf, int buf_size) {
+ FILE *fd = (FILE*)userdata;
+ if(!feof(fd)) {
+ return fread(buf, 1, buf_size, fd);
+ }
+ return -1;
+}
+
+int main(int argc, char *argv[]) {
+ int err = 0, ret = 0;
+ const char* filename = NULL;
+ SDL_Window *window = NULL;
+ SDL_Renderer *renderer = NULL;
+ bool run = true;
+ Kit_Source *src = NULL;
+ Kit_Player *player = NULL;
+ SDL_AudioSpec wanted_spec, audio_spec;
+ SDL_AudioDeviceID audio_dev;
+
+ // Get filename to open
+ if(argc != 2) {
+ fprintf(stderr, "Usage: custom <filename>\n");
+ return 0;
+ }
+ filename = argv[1];
+
+ // Init SDL
+ err = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize SDL2!\n");
+ return 1;
+ }
+
+ // Create a resizable window.
+ window = SDL_CreateWindow("Example Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a new window!\n");
+ return 1;
+ }
+
+ // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay.
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a renderer!\n");
+ return 1;
+ }
+
+ // Initialize Kitchensink with network and libass support.
+ err = Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError());
+ return 1;
+ }
+
+ // Open file with fopen. We then proceed to read this with our custom file handlers.
+ FILE *fd = fopen(filename, "rb");
+ if(fd == NULL) {
+ fprintf(stderr, "Unable to open file '%s' for reading\n", filename);
+ return 1;
+ }
+
+ // Open up the custom source. Declare read callback, and transport FD in userdata.
+ src = Kit_CreateSourceFromCustom(read_callback, NULL, fd);
+ if(src == NULL) {
+ fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError());
+ return 1;
+ }
+
+ // Create the player. Pick best video, audio and subtitle streams, and set subtitle
+ // rendering resolution to screen resolution.
+ player = Kit_CreatePlayer(
+ src,
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE),
+ 1280, 720);
+ if(player == NULL) {
+ fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
+ return 1;
+ }
+
+ // Print some information
+ Kit_PlayerInfo pinfo;
+ Kit_GetPlayerInfo(player, &pinfo);
+
+ // Make sure there is video in the file to play first.
+ if(Kit_GetPlayerVideoStream(player) == -1) {
+ fprintf(stderr, "File contains no video!\n");
+ return 1;
+ }
+
+ // Init audio
+ SDL_memset(&wanted_spec, 0, sizeof(wanted_spec));
+ wanted_spec.freq = pinfo.audio.output.samplerate;
+ wanted_spec.format = pinfo.audio.output.format;
+ wanted_spec.channels = pinfo.audio.output.channels;
+ audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0);
+ SDL_PauseAudioDevice(audio_dev, 0);
+
+ // Initialize video texture. This will probably end up as YV12 most of the time.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
+ SDL_Texture *video_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.video.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ pinfo.video.output.width,
+ pinfo.video.output.height);
+ if(video_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a video texture\n");
+ return 1;
+ }
+
+ // This is the subtitle texture atlas. This contains all the subtitle image fragments.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations
+ SDL_Texture *subtitle_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.subtitle.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ ATLAS_WIDTH, ATLAS_HEIGHT);
+ if(subtitle_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n");
+ return 1;
+ }
+
+ // Make sure subtitle texture is in correct blendmode
+ SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND);
+
+ // Clear screen with black
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
+
+ // Start playback
+ Kit_PlayerPlay(player);
+
+ // Playback temporary data buffers
+ char audiobuf[AUDIOBUFFER_SIZE];
+ SDL_Rect sources[ATLAS_MAX];
+ SDL_Rect targets[ATLAS_MAX];
+
+ // Get movie area size
+ SDL_RenderSetLogicalSize(renderer, pinfo.video.output.width, pinfo.video.output.height);
+ while(run) {
+ if(Kit_GetPlayerState(player) == KIT_STOPPED) {
+ run = false;
+ continue;
+ }
+
+ SDL_Event event;
+ while(SDL_PollEvent(&event)) {
+ switch(event.type) {
+ case SDL_QUIT:
+ run = false;
+ break;
+ case SDL_KEYUP:
+ if(event.key.keysym.sym == SDLK_RIGHT)
+ Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10);
+ if(event.key.keysym.sym == SDLK_LEFT)
+ Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10);
+ break;
+ }
+ }
+
+ // Refresh audio
+ int queued = SDL_GetQueuedAudioSize(audio_dev);
+ if(queued < AUDIOBUFFER_SIZE) {
+ int need = AUDIOBUFFER_SIZE - queued;
+
+ while(need > 0) {
+ ret = Kit_GetPlayerAudioData(
+ player,
+ (unsigned char*)audiobuf,
+ AUDIOBUFFER_SIZE);
+ need -= ret;
+ if(ret > 0) {
+ SDL_QueueAudio(audio_dev, audiobuf, ret);
+ } else {
+ break;
+ }
+ }
+ // If we now have data, start playback (again)
+ if(SDL_GetQueuedAudioSize(audio_dev) > 0) {
+ SDL_PauseAudioDevice(audio_dev, 0);
+ }
+ }
+
+ // Refresh videotexture and render it
+ Kit_GetPlayerVideoData(player, video_tex);
+ SDL_RenderCopy(renderer, video_tex, NULL, NULL);
+
+ // Refresh subtitle texture atlas and render subtitle frames from it
+ // For subtitles, use screen size instead of video size for best quality
+ int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, ATLAS_MAX);
+ for(int i = 0; i < got; i++) {
+ SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]);
+ }
+
+ // Render to screen + wait for vsync
+ SDL_RenderPresent(renderer);
+ }
+
+ Kit_ClosePlayer(player);
+ Kit_CloseSource(src);
+ fclose(fd);
+ Kit_Quit();
+
+ SDL_DestroyTexture(subtitle_tex);
+ SDL_DestroyTexture(video_tex);
+ SDL_CloseAudioDevice(audio_dev);
+
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+}
diff --git a/examples/example_rwops.c b/examples/example_rwops.c
new file mode 100644
index 0000000..84370f1
--- /dev/null
+++ b/examples/example_rwops.c
@@ -0,0 +1,223 @@
+#include <kitchensink/kitchensink.h>
+#include <SDL.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+/*
+* Note! This example does not do proper error handling etc.
+* It is for example use only!
+*/
+
+#define AUDIOBUFFER_SIZE (1024 * 64)
+#define ATLAS_WIDTH 4096
+#define ATLAS_HEIGHT 4096
+#define ATLAS_MAX 1024
+
+
+int main(int argc, char *argv[]) {
+ int err = 0, ret = 0;
+ const char* filename = NULL;
+ SDL_Window *window = NULL;
+ SDL_Renderer *renderer = NULL;
+ bool run = true;
+ Kit_Source *src = NULL;
+ Kit_Player *player = NULL;
+ SDL_AudioSpec wanted_spec, audio_spec;
+ SDL_AudioDeviceID audio_dev;
+
+ // Get filename to open
+ if(argc != 2) {
+ fprintf(stderr, "Usage: custom <filename>\n");
+ return 0;
+ }
+ filename = argv[1];
+
+ // Init SDL
+ err = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize SDL2!\n");
+ return 1;
+ }
+
+ // Create a resizable window.
+ window = SDL_CreateWindow("Example Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a new window!\n");
+ return 1;
+ }
+
+ // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay.
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a renderer!\n");
+ return 1;
+ }
+
+ // Initialize Kitchensink with network and libass support.
+ err = Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError());
+ return 1;
+ }
+
+ // Open file with fopen. We then proceed to read this with our custom file handlers.
+ SDL_RWops *rw_ops = SDL_RWFromFile(filename, "rb");
+ if(rw_ops == NULL) {
+ fprintf(stderr, "Unable to open file '%s' for reading\n", filename);
+ return 1;
+ }
+
+ // Open up the SDL RWops source
+ src = Kit_CreateSourceFromRW(rw_ops);
+ if(src == NULL) {
+ fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError());
+ return 1;
+ }
+
+ // Create the player. Pick best video, audio and subtitle streams, and set subtitle
+ // rendering resolution to screen resolution.
+ player = Kit_CreatePlayer(
+ src,
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE),
+ 1280, 720);
+ if(player == NULL) {
+ fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
+ return 1;
+ }
+
+ // Print some information
+ Kit_PlayerInfo pinfo;
+ Kit_GetPlayerInfo(player, &pinfo);
+
+ // Make sure there is video in the file to play first.
+ if(Kit_GetPlayerVideoStream(player) == -1) {
+ fprintf(stderr, "File contains no video!\n");
+ return 1;
+ }
+
+ // Init audio
+ SDL_memset(&wanted_spec, 0, sizeof(wanted_spec));
+ wanted_spec.freq = pinfo.audio.output.samplerate;
+ wanted_spec.format = pinfo.audio.output.format;
+ wanted_spec.channels = pinfo.audio.output.channels;
+ audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0);
+ SDL_PauseAudioDevice(audio_dev, 0);
+
+ // Initialize video texture. This will probably end up as YV12 most of the time.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
+ SDL_Texture *video_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.video.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ pinfo.video.output.width,
+ pinfo.video.output.height);
+ if(video_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a video texture\n");
+ return 1;
+ }
+
+ // This is the subtitle texture atlas. This contains all the subtitle image fragments.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations
+ SDL_Texture *subtitle_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.subtitle.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ ATLAS_WIDTH, ATLAS_HEIGHT);
+ if(subtitle_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n");
+ return 1;
+ }
+
+ // Make sure subtitle texture is in correct blendmode
+ SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND);
+
+ // Clear screen with black
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
+
+ // Start playback
+ Kit_PlayerPlay(player);
+
+ // Playback temporary data buffers
+ char audiobuf[AUDIOBUFFER_SIZE];
+ SDL_Rect sources[ATLAS_MAX];
+ SDL_Rect targets[ATLAS_MAX];
+
+ // Get movie area size
+ SDL_RenderSetLogicalSize(renderer, pinfo.video.output.width, pinfo.video.output.height);
+ while(run) {
+ if(Kit_GetPlayerState(player) == KIT_STOPPED) {
+ run = false;
+ continue;
+ }
+
+ SDL_Event event;
+ while(SDL_PollEvent(&event)) {
+ switch(event.type) {
+ case SDL_QUIT:
+ run = false;
+ break;
+ case SDL_KEYUP:
+ if(event.key.keysym.sym == SDLK_RIGHT)
+ Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10);
+ if(event.key.keysym.sym == SDLK_LEFT)
+ Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10);
+ break;
+
+ }
+ }
+
+ // Refresh audio
+ int queued = SDL_GetQueuedAudioSize(audio_dev);
+ if(queued < AUDIOBUFFER_SIZE) {
+ int need = AUDIOBUFFER_SIZE - queued;
+
+ while(need > 0) {
+ ret = Kit_GetPlayerAudioData(
+ player,
+ (unsigned char*)audiobuf,
+ AUDIOBUFFER_SIZE);
+ need -= ret;
+ if(ret > 0) {
+ SDL_QueueAudio(audio_dev, audiobuf, ret);
+ } else {
+ break;
+ }
+ }
+ // If we now have data, start playback (again)
+ if(SDL_GetQueuedAudioSize(audio_dev) > 0) {
+ SDL_PauseAudioDevice(audio_dev, 0);
+ }
+ }
+
+ // Refresh videotexture and render it
+ Kit_GetPlayerVideoData(player, video_tex);
+ SDL_RenderCopy(renderer, video_tex, NULL, NULL);
+
+ // Refresh subtitle texture atlas and render subtitle frames from it
+ // For subtitles, use screen size instead of video size for best quality
+ int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, ATLAS_MAX);
+ for(int i = 0; i < got; i++) {
+ SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]);
+ }
+
+ // Render to screen + wait for vsync
+ SDL_RenderPresent(renderer);
+ }
+
+ Kit_ClosePlayer(player);
+ Kit_CloseSource(src);
+ SDL_RWclose(rw_ops);
+ Kit_Quit();
+
+ SDL_DestroyTexture(subtitle_tex);
+ SDL_DestroyTexture(video_tex);
+ SDL_CloseAudioDevice(audio_dev);
+
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+}
diff --git a/examples/example_simple.c b/examples/example_simple.c
new file mode 100644
index 0000000..4c581c0
--- /dev/null
+++ b/examples/example_simple.c
@@ -0,0 +1,208 @@
+#include <kitchensink/kitchensink.h>
+#include <SDL.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+/*
+* Note! This example does not do proper error handling etc.
+* It is for example use only!
+*/
+
+#define AUDIOBUFFER_SIZE (1024 * 64)
+#define ATLAS_WIDTH 4096
+#define ATLAS_HEIGHT 4096
+#define ATLAS_MAX 1024
+
+int main(int argc, char *argv[]) {
+ int err = 0, ret = 0;
+ const char* filename = NULL;
+ SDL_Window *window = NULL;
+ SDL_Renderer *renderer = NULL;
+ bool run = true;
+ Kit_Source *src = NULL;
+ Kit_Player *player = NULL;
+ SDL_AudioSpec wanted_spec, audio_spec;
+ SDL_AudioDeviceID audio_dev;
+
+ // Get filename to open
+ if(argc != 2) {
+ fprintf(stderr, "Usage: simple <filename>\n");
+ return 0;
+ }
+ filename = argv[1];
+
+ // Init SDL
+ err = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize SDL2!\n");
+ return 1;
+ }
+
+ // Create a resizable window.
+ window = SDL_CreateWindow("Example Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a new window!\n");
+ return 1;
+ }
+
+ // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay.
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
+ if(window == NULL) {
+ fprintf(stderr, "Unable to create a renderer!\n");
+ return 1;
+ }
+
+ // Initialize Kitchensink with network and libass support.
+ err = Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS);
+ if(err != 0) {
+ fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError());
+ return 1;
+ }
+
+ // Open up the sourcefile.
+ // This can be a local file, network url, ...
+ src = Kit_CreateSourceFromUrl(filename);
+ if(src == NULL) {
+ fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError());
+ return 1;
+ }
+
+ // Create the player. Pick best video, audio and subtitle streams, and set subtitle
+ // rendering resolution to screen resolution.
+ player = Kit_CreatePlayer(
+ src,
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE),
+ 1280, 720);
+ if(player == NULL) {
+ fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
+ return 1;
+ }
+
+ // Print some information
+ Kit_PlayerInfo pinfo;
+ Kit_GetPlayerInfo(player, &pinfo);
+
+ // Make sure there is video in the file to play first.
+ if(Kit_GetPlayerVideoStream(player) == -1) {
+ fprintf(stderr, "File contains no video!\n");
+ return 1;
+ }
+
+ // Init audio
+ SDL_memset(&wanted_spec, 0, sizeof(wanted_spec));
+ wanted_spec.freq = pinfo.audio.output.samplerate;
+ wanted_spec.format = pinfo.audio.output.format;
+ wanted_spec.channels = pinfo.audio.output.channels;
+ audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0);
+ SDL_PauseAudioDevice(audio_dev, 0);
+
+ // Initialize video texture. This will probably end up as YV12 most of the time.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
+ SDL_Texture *video_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.video.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ pinfo.video.output.width,
+ pinfo.video.output.height);
+ if(video_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a video texture\n");
+ return 1;
+ }
+
+ // This is the subtitle texture atlas. This contains all the subtitle image fragments.
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations
+ SDL_Texture *subtitle_tex = SDL_CreateTexture(
+ renderer,
+ pinfo.subtitle.output.format,
+ SDL_TEXTUREACCESS_STATIC,
+ ATLAS_WIDTH, ATLAS_HEIGHT);
+ if(subtitle_tex == NULL) {
+ fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n");
+ return 1;
+ }
+
+ // Make sure subtitle texture is in correct blendmode
+ SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND);
+
+ // Clear screen with black
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderClear(renderer);
+
+ // Start playback
+ Kit_PlayerPlay(player);
+
+ // Playback temporary data buffers
+ char audiobuf[AUDIOBUFFER_SIZE];
+ SDL_Rect sources[ATLAS_MAX];
+ SDL_Rect targets[ATLAS_MAX];
+
+ // Get movie area size
+ SDL_RenderSetLogicalSize(renderer, pinfo.video.output.width, pinfo.video.output.height);
+ while(run) {
+ if(Kit_GetPlayerState(player) == KIT_STOPPED) {
+ run = false;
+ continue;
+ }
+
+ SDL_Event event;
+ while(SDL_PollEvent(&event)) {
+ switch(event.type) {
+ case SDL_QUIT:
+ run = false;
+ break;
+ }
+ }
+
+ // Refresh audio
+ int queued = SDL_GetQueuedAudioSize(audio_dev);
+ if(queued < AUDIOBUFFER_SIZE) {
+ int need = AUDIOBUFFER_SIZE - queued;
+
+ while(need > 0) {
+ ret = Kit_GetPlayerAudioData(
+ player,
+ (unsigned char*)audiobuf,
+ AUDIOBUFFER_SIZE);
+ need -= ret;
+ if(ret > 0) {
+ SDL_QueueAudio(audio_dev, audiobuf, ret);
+ } else {
+ break;
+ }
+ }
+ // If we now have data, start playback (again)
+ if(SDL_GetQueuedAudioSize(audio_dev) > 0) {
+ SDL_PauseAudioDevice(audio_dev, 0);
+ }
+ }
+
+ // Refresh videotexture and render it
+ Kit_GetPlayerVideoData(player, video_tex);
+ SDL_RenderCopy(renderer, video_tex, NULL, NULL);
+
+ // Refresh subtitle texture atlas and render subtitle frames from it
+ // For subtitles, use screen size instead of video size for best quality
+ int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, ATLAS_MAX);
+ for(int i = 0; i < got; i++) {
+ SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]);
+ }
+
+ // Render to screen + wait for vsync
+ SDL_RenderPresent(renderer);
+ }
+
+ Kit_ClosePlayer(player);
+ Kit_CloseSource(src);
+ Kit_Quit();
+
+ SDL_DestroyTexture(subtitle_tex);
+ SDL_DestroyTexture(video_tex);
+ SDL_CloseAudioDevice(audio_dev);
+
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+}
diff --git a/include/kitchensink/internal/audio/kitaudio.h b/include/kitchensink/internal/audio/kitaudio.h
new file mode 100644
index 0000000..e42770b
--- /dev/null
+++ b/include/kitchensink/internal/audio/kitaudio.h
@@ -0,0 +1,11 @@
+#ifndef KITAUDIO_H
+#define KITAUDIO_H
+
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/kitsource.h"
+#include "kitchensink/internal/kitdecoder.h"
+
+KIT_LOCAL Kit_Decoder* Kit_CreateAudioDecoder(const Kit_Source *src, int stream_index);
+KIT_LOCAL int Kit_GetAudioDecoderData(Kit_Decoder *dec, unsigned char *buf, int len);
+
+#endif // KITAUDIO_H
diff --git a/include/kitchensink/internal/kitdecoder.h b/include/kitchensink/internal/kitdecoder.h
new file mode 100644
index 0000000..ef3edb7
--- /dev/null
+++ b/include/kitchensink/internal/kitdecoder.h
@@ -0,0 +1,75 @@
+#ifndef KITDECODER_H
+#define KITDECODER_H
+
+#include <stdbool.h>
+
+#include <SDL_mutex.h>
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+#include "kitchensink/kitformat.h"
+#include "kitchensink/kitcodec.h"
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/kitsource.h"
+#include "kitchensink/internal/utils/kitbuffer.h"
+
+enum {
+ KIT_DEC_BUF_IN = 0,
+ KIT_DEC_BUF_OUT,
+ KIT_DEC_BUF_COUNT
+};
+
+typedef struct Kit_Decoder Kit_Decoder;
+
+typedef void (*dec_decode_cb)(Kit_Decoder *dec, AVPacket *in_packet);
+typedef void (*dec_close_cb)(Kit_Decoder *dec);
+typedef void (*dec_free_packet_cb)(void *packet);
+
+KIT_LOCAL struct Kit_Decoder {
+ int stream_index; ///< Source stream index for the current stream
+ double clock_sync; ///< Sync source for current stream
+ double clock_pos; ///< Current pts for the stream
+ Kit_OutputFormat output; ///< Output format for the decoder
+
+ AVCodecContext *codec_ctx; ///< FFMpeg internal: Codec context
+ AVFormatContext *format_ctx; ///< FFMpeg internal: Format context (owner: Kit_Source)
+
+ SDL_mutex *output_lock; ///< Threading lock for output buffer
+ Kit_Buffer *buffer[2]; ///< Buffers for incoming and decoded packets
+
+ void *userdata; ///< Decoder specific information (Audio, video, subtitle context)
+ dec_decode_cb dec_decode; ///< Decoder decoding function callback
+ dec_close_cb dec_close; ///< Decoder close function callback
+};
+
+KIT_LOCAL Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index,
+ int out_b_size, dec_free_packet_cb free_out_cb,
+ int thread_count);
+KIT_LOCAL void Kit_CloseDecoder(Kit_Decoder *dec);
+
+KIT_LOCAL int Kit_GetDecoderStreamIndex(const Kit_Decoder *dec);
+KIT_LOCAL int Kit_GetDecoderCodecInfo(const Kit_Decoder *dec, Kit_Codec *codec);
+KIT_LOCAL int Kit_GetDecoderOutputFormat(const Kit_Decoder *dec, Kit_OutputFormat *output);
+
+KIT_LOCAL void Kit_SetDecoderClockSync(Kit_Decoder *dec, double sync);
+KIT_LOCAL void Kit_ChangeDecoderClockSync(Kit_Decoder *dec, double sync);
+
+KIT_LOCAL int Kit_RunDecoder(Kit_Decoder *dec);
+KIT_LOCAL void Kit_ClearDecoderBuffers(Kit_Decoder *dec);
+
+KIT_LOCAL bool Kit_CanWriteDecoderInput(Kit_Decoder *dec);
+KIT_LOCAL int Kit_WriteDecoderInput(Kit_Decoder *dec, AVPacket *packet);
+KIT_LOCAL AVPacket* Kit_ReadDecoderInput(Kit_Decoder *dec);
+KIT_LOCAL void Kit_ClearDecoderInput(Kit_Decoder *dec);
+
+KIT_LOCAL int Kit_WriteDecoderOutput(Kit_Decoder *dec, void *packet);
+KIT_LOCAL void* Kit_PeekDecoderOutput(Kit_Decoder *dec);
+KIT_LOCAL void* Kit_ReadDecoderOutput(Kit_Decoder *dec);
+KIT_LOCAL void Kit_AdvanceDecoderOutput(Kit_Decoder *dec);
+KIT_LOCAL void Kit_ForEachDecoderOutput(Kit_Decoder *dec, Kit_ForEachItemCallback foreach_cb, void *userdata);
+KIT_LOCAL int Kit_LockDecoderOutput(Kit_Decoder *dec);
+KIT_LOCAL void Kit_UnlockDecoderOutput(Kit_Decoder *dec);
+KIT_LOCAL void Kit_ClearDecoderOutput(Kit_Decoder *dec);
+
+
+#endif // KITDECODER_H
diff --git a/include/kitchensink/internal/kitlibstate.h b/include/kitchensink/internal/kitlibstate.h
index e16a5c9..b92cebb 100644
--- a/include/kitchensink/internal/kitlibstate.h
+++ b/include/kitchensink/internal/kitlibstate.h
@@ -1,12 +1,18 @@
#ifndef KITLIBSTATE_H
#define KITLIBSTATE_H
-#include <ass/ass.h>
+#include "kitchensink/internal/libass.h"
#include "kitchensink/kitconfig.h"
typedef struct Kit_LibraryState {
unsigned int init_flags;
+ unsigned int thread_count;
+ unsigned int font_hinting;
+ unsigned int video_buf_frames;
+ unsigned int audio_buf_frames;
+ unsigned int subtitle_buf_frames;
ASS_Library *libass_handle;
+ void *ass_so_handle;
} Kit_LibraryState;
KIT_LOCAL Kit_LibraryState* Kit_GetLibraryState();
diff --git a/include/kitchensink/internal/kitlist.h b/include/kitchensink/internal/kitlist.h
deleted file mode 100644
index 85e3e3f..0000000
--- a/include/kitchensink/internal/kitlist.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef KITLIST_H
-#define KITLIST_H
-
-#include "kitchensink/kitconfig.h"
-
-typedef struct Kit_List Kit_List;
-
-typedef void (*Kit_ListFreeCallback)(void*);
-
-struct Kit_List {
- unsigned int size;
- unsigned int length;
- Kit_ListFreeCallback free_cb;
- void **data;
-};
-
-KIT_LOCAL Kit_List* Kit_CreateList(unsigned int size, Kit_ListFreeCallback free_cb);
-KIT_LOCAL void Kit_DestroyList(Kit_List *list);
-
-KIT_LOCAL void Kit_ClearList(Kit_List *list);
-KIT_LOCAL void Kit_RemoveFromList(Kit_List *list, unsigned int iterator);
-KIT_LOCAL void* Kit_IterateList(const Kit_List *list, unsigned int *iterator);
-KIT_LOCAL int Kit_WriteList(Kit_List *list, void *ptr);
-KIT_LOCAL int Kit_GetListLength(const Kit_List *list);
-
-#endif // KITLIST_H
diff --git a/include/kitchensink/internal/libass.h b/include/kitchensink/internal/libass.h
new file mode 100644
index 0000000..0f91d95
--- /dev/null
+++ b/include/kitchensink/internal/libass.h
@@ -0,0 +1,66 @@
+#ifndef KITLIBASS_H
+#define KITLIBASS_H
+
+#ifndef USE_DYNAMIC_LIBASS
+
+#include <ass/ass.h>
+
+#else // USE_DYNAMIC_LIBASS
+
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "kitchensink/kitconfig.h"
+
+typedef struct ass_library ASS_Library;
+typedef struct ass_renderer ASS_Renderer;
+typedef struct ass_track ASS_Track;
+
+typedef struct ass_image {
+ int w, h;
+ int stride;
+ unsigned char *bitmap;
+ uint32_t color;
+ int dst_x, dst_y;
+ struct ass_image *next;
+ enum {
+ IMAGE_TYPE_CHARACTER,
+ IMAGE_TYPE_OUTLINE,
+ IMAGE_TYPE_SHADOW
+ } type;
+} ASS_Image;
+
+typedef enum {
+ ASS_HINTING_NONE = 0,
+ ASS_HINTING_LIGHT,
+ ASS_HINTING_NORMAL,
+ ASS_HINTING_NATIVE
+} ASS_Hinting;
+
+KIT_LOCAL ASS_Library* (*ass_library_init)(void);
+KIT_LOCAL void (*ass_library_done)(ASS_Library *priv);
+KIT_LOCAL void (*ass_process_codec_private)(ASS_Track *track, char *data, int size);
+KIT_LOCAL void (*ass_set_message_cb)(ASS_Library *priv, void (*msg_cb)(int level, const char *fmt, va_list args, void *data), void *data);
+KIT_LOCAL ASS_Renderer* (*ass_renderer_init)(ASS_Library *);
+KIT_LOCAL void (*ass_renderer_done)(ASS_Renderer *priv);
+KIT_LOCAL void (*ass_set_frame_size)(ASS_Renderer *priv, int w, int h);
+KIT_LOCAL void (*ass_set_hinting)(ASS_Renderer *priv, ASS_Hinting ht);
+KIT_LOCAL void (*ass_set_fonts)(ASS_Renderer *priv, const char *default_font, const char *default_family, int dfp, const char *config, int update);
+KIT_LOCAL ASS_Image* (*ass_render_frame)(ASS_Renderer *priv, ASS_Track *track, long long now, int *detect_change);
+KIT_LOCAL ASS_Track* (*ass_new_track)(ASS_Library *);
+KIT_LOCAL void (*ass_free_track)(ASS_Track *track);
+KIT_LOCAL void (*ass_process_data)(ASS_Track *track, char *data, int size);
+KIT_LOCAL void (*ass_process_chunk)(ASS_Track *track, char *data, int size, long long timecode, long long duration);
+KIT_LOCAL void (*ass_add_font)(ASS_Library *library, char *name, char *data, int data_size);
+KIT_LOCAL void (*ass_set_storage_size)(ASS_Renderer *priv, int w, int h);
+
+KIT_LOCAL int load_libass(void *handle);
+
+#endif // USE_DYNAMIC_LIBASS
+
+// For compatibility
+#ifndef ASS_FONTPROVIDER_AUTODETECT
+#define ASS_FONTPROVIDER_AUTODETECT 1
+#endif
+
+#endif // KITLIBASS_H
diff --git a/include/kitchensink/internal/subtitle/kitatlas.h b/include/kitchensink/internal/subtitle/kitatlas.h
new file mode 100644
index 0000000..d9207c1
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/kitatlas.h
@@ -0,0 +1,40 @@
+#ifndef KITATLAS_H
+#define KITATLAS_H
+
+#include <stdbool.h>
+#include <SDL_rect.h>
+#include <SDL_render.h>
+
+#include "kitchensink/kitconfig.h"
+
+typedef struct Kit_TextureAtlasItem {
+ int cur_shelf; //< Current shelf number in cache
+ int cur_slot; //< Current slot on shelf in cache
+ SDL_Rect source; //< Source coordinates on cache surface
+ SDL_Rect target; //< Target coordinates on output surface
+} Kit_TextureAtlasItem;
+
+typedef struct Kit_Shelf {
+ uint16_t width;
+ uint16_t height;
+ uint16_t count;
+} Kit_Shelf;
+
+typedef struct Kit_TextureAtlas {
+ int cur_items; //< Current items count
+ int max_items; //< Maximum items count
+ int max_shelves; //< Maximum shelf count
+ int w; //< Current atlas width
+ int h; //< Current atlas height
+ Kit_TextureAtlasItem *items; //< Cached items
+ Kit_Shelf *shelves; //< Atlas shelves
+} Kit_TextureAtlas;
+
+KIT_LOCAL Kit_TextureAtlas* Kit_CreateAtlas();
+KIT_LOCAL void Kit_FreeAtlas(Kit_TextureAtlas *atlas);
+KIT_LOCAL void Kit_ClearAtlasContent(Kit_TextureAtlas *atlas);
+KIT_LOCAL void Kit_CheckAtlasTextureSize(Kit_TextureAtlas *atlas, SDL_Texture *texture);
+KIT_LOCAL int Kit_GetAtlasItems(const Kit_TextureAtlas *atlas, SDL_Rect *sources, SDL_Rect *targets, int limit);
+KIT_LOCAL int Kit_AddAtlasItem(Kit_TextureAtlas *atlas, SDL_Texture *texture, SDL_Surface *surface, const SDL_Rect *target);
+
+#endif // KITATLAS_H
diff --git a/include/kitchensink/internal/subtitle/kitsubtitle.h b/include/kitchensink/internal/subtitle/kitsubtitle.h
new file mode 100644
index 0000000..8bb2a62
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/kitsubtitle.h
@@ -0,0 +1,17 @@
+#ifndef KITSUBTITLE_H
+#define KITSUBTITLE_H
+
+#include <SDL_render.h>
+
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/kitsource.h"
+#include "kitchensink/internal/kitdecoder.h"
+
+KIT_LOCAL Kit_Decoder* Kit_CreateSubtitleDecoder(
+ const Kit_Source *src, int stream_index, int video_w, int video_h, int screen_w, int screen_h);
+KIT_LOCAL void Kit_GetSubtitleDecoderTexture(Kit_Decoder *dec, SDL_Texture *texture);
+KIT_LOCAL void Kit_SetSubtitleDecoderSize(Kit_Decoder *dec, int w, int h);
+KIT_LOCAL int Kit_GetSubtitleDecoderInfo(
+ Kit_Decoder *dec, SDL_Texture *texture, SDL_Rect *sources, SDL_Rect *targets, int limit);
+
+#endif // KITSUBTITLE_H
diff --git a/include/kitchensink/internal/subtitle/kitsubtitlepacket.h b/include/kitchensink/internal/subtitle/kitsubtitlepacket.h
new file mode 100644
index 0000000..c560c74
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/kitsubtitlepacket.h
@@ -0,0 +1,22 @@
+#ifndef KITSUBTITLEPACKET_H
+#define KITSUBTITLEPACKET_H
+
+#include <stdbool.h>
+#include <SDL_surface.h>
+
+#include "kitchensink/kitconfig.h"
+
+typedef struct Kit_SubtitlePacket {
+ double pts_start;
+ double pts_end;
+ int x;
+ int y;
+ bool clear;
+ SDL_Surface *surface;
+} Kit_SubtitlePacket;
+
+KIT_LOCAL Kit_SubtitlePacket* Kit_CreateSubtitlePacket(
+ bool clear, double pts_start, double pts_end, int pos_x, int pos_y, SDL_Surface *surface);
+KIT_LOCAL void Kit_FreeSubtitlePacket(Kit_SubtitlePacket *packet);
+
+#endif // KITSUBTITLEPACKET_H
diff --git a/include/kitchensink/internal/subtitle/renderers/kitsubass.h b/include/kitchensink/internal/subtitle/renderers/kitsubass.h
new file mode 100644
index 0000000..6dff50c
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/renderers/kitsubass.h
@@ -0,0 +1,11 @@
+#ifndef KITSUBASS_H
+#define KITSUBASS_H
+
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+
+KIT_LOCAL Kit_SubtitleRenderer* Kit_CreateASSSubtitleRenderer(
+ Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h);
+
+#endif // KITSUBASS_H
diff --git a/include/kitchensink/internal/subtitle/renderers/kitsubimage.h b/include/kitchensink/internal/subtitle/renderers/kitsubimage.h
new file mode 100644
index 0000000..883fde3
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/renderers/kitsubimage.h
@@ -0,0 +1,11 @@
+#ifndef KITSUBIMAGE_H
+#define KITSUBIMAGE_H
+
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+
+KIT_LOCAL Kit_SubtitleRenderer* Kit_CreateImageSubtitleRenderer(
+ Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h);
+
+#endif // KITSUBIMAGE_H
diff --git a/include/kitchensink/internal/subtitle/renderers/kitsubrenderer.h b/include/kitchensink/internal/subtitle/renderers/kitsubrenderer.h
new file mode 100644
index 0000000..3c37b00
--- /dev/null
+++ b/include/kitchensink/internal/subtitle/renderers/kitsubrenderer.h
@@ -0,0 +1,32 @@
+#ifndef KITSUBRENDERER_H
+#define KITSUBRENDERER_H
+
+#include <SDL_render.h>
+
+#include "kitchensink/kitsource.h"
+
+typedef struct Kit_SubtitleRenderer Kit_SubtitleRenderer;
+typedef struct Kit_TextureAtlas Kit_TextureAtlas;
+typedef struct Kit_Decoder Kit_Decoder;
+
+typedef void (*ren_render_cb)(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts);
+typedef int (*ren_get_data_cb)(Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts);
+typedef void (*ren_set_size_cb)(Kit_SubtitleRenderer *ren, int w, int h);
+typedef void (*ren_close_cb)(Kit_SubtitleRenderer *ren);
+
+struct Kit_SubtitleRenderer {
+ Kit_Decoder *dec;
+ void *userdata;
+ ren_render_cb ren_render; ///< Subtitle rendering function callback
+ ren_get_data_cb ren_get_data; ///< Subtitle data getter function callback
+ ren_set_size_cb ren_set_size; ///< Screen size setter function callback
+ ren_close_cb ren_close; ///< Subtitle renderer close function callback
+};
+
+KIT_LOCAL Kit_SubtitleRenderer* Kit_CreateSubtitleRenderer(Kit_Decoder *dec);
+KIT_LOCAL void Kit_RunSubtitleRenderer(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts);
+KIT_LOCAL int Kit_GetSubtitleRendererData(Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts);
+KIT_LOCAL void Kit_SetSubtitleRendererSize(Kit_SubtitleRenderer *ren, int w, int h);
+KIT_LOCAL void Kit_CloseSubtitleRenderer(Kit_SubtitleRenderer *ren);
+
+#endif // KITSUBRENDERER_H
diff --git a/include/kitchensink/internal/kitbuffer.h b/include/kitchensink/internal/utils/kitbuffer.h
index 4d0f8cc..67d93c3 100644
--- a/include/kitchensink/internal/kitbuffer.h
+++ b/include/kitchensink/internal/utils/kitbuffer.h
@@ -6,6 +6,7 @@
typedef struct Kit_Buffer Kit_Buffer;
typedef void (*Kit_BufferFreeCallback)(void*);
+typedef void (*Kit_ForEachItemCallback)(void*, void *userdata);
struct Kit_Buffer {
unsigned int read_p;
@@ -23,6 +24,7 @@ KIT_LOCAL void* Kit_ReadBuffer(Kit_Buffer *buffer);
KIT_LOCAL void* Kit_PeekBuffer(const Kit_Buffer *buffer);
KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer);
KIT_LOCAL int Kit_WriteBuffer(Kit_Buffer *buffer, void *ptr);
+KIT_LOCAL void Kit_ForEachItemInBuffer(const Kit_Buffer *buffer, Kit_ForEachItemCallback cb, void *userdata);
KIT_LOCAL int Kit_IsBufferFull(const Kit_Buffer *buffer);
#endif // KITBUFFER_H
diff --git a/include/kitchensink/internal/utils/kithelpers.h b/include/kitchensink/internal/utils/kithelpers.h
new file mode 100644
index 0000000..5b94a7a
--- /dev/null
+++ b/include/kitchensink/internal/utils/kithelpers.h
@@ -0,0 +1,11 @@
+#ifndef KITHELPERS_H
+#define KITHELPERS_H
+
+#include <stdbool.h>
+#include <libavformat/avformat.h>
+#include "kitchensink/kitconfig.h"
+
+KIT_LOCAL double _GetSystemTime();
+KIT_LOCAL bool attachment_is_font(AVStream *stream);
+
+#endif // KITHELPERS_H
diff --git a/include/kitchensink/internal/utils/kitlog.h b/include/kitchensink/internal/utils/kitlog.h
new file mode 100644
index 0000000..d298592
--- /dev/null
+++ b/include/kitchensink/internal/utils/kitlog.h
@@ -0,0 +1,11 @@
+#ifndef KITLOG_H
+#define KITLOG_H
+
+#ifdef NDEBUG
+#define LOG(...)
+#else
+#include <stdio.h>
+#define LOG(...) fprintf(stderr, __VA_ARGS__); fflush(stderr)
+#endif
+
+#endif // KITLOG_H
diff --git a/include/kitchensink/internal/kitringbuffer.h b/include/kitchensink/internal/utils/kitringbuffer.h
index 2f67520..153dfd4 100644
--- a/include/kitchensink/internal/kitringbuffer.h
+++ b/include/kitchensink/internal/utils/kitringbuffer.h
@@ -6,7 +6,8 @@
typedef struct Kit_RingBuffer {
int size;
int len;
- int wpos, rpos;
+ int wpos;
+ int rpos;
char* data;
} Kit_RingBuffer;
diff --git a/include/kitchensink/internal/video/kitvideo.h b/include/kitchensink/internal/video/kitvideo.h
new file mode 100644
index 0000000..5c072ef
--- /dev/null
+++ b/include/kitchensink/internal/video/kitvideo.h
@@ -0,0 +1,13 @@
+#ifndef KITVIDEO_H
+#define KITVIDEO_H
+
+#include <SDL_render.h>
+
+#include "kitchensink/kitconfig.h"
+#include "kitchensink/kitsource.h"
+#include "kitchensink/internal/kitdecoder.h"
+
+KIT_LOCAL Kit_Decoder* Kit_CreateVideoDecoder(const Kit_Source *src, int stream_index);
+KIT_LOCAL int Kit_GetVideoDecoderData(Kit_Decoder *dec, SDL_Texture *texture);
+
+#endif // KITVIDEO_H
diff --git a/include/kitchensink/kitchensink.h b/include/kitchensink/kitchensink.h
index be318a5..7fc08e6 100644
--- a/include/kitchensink/kitchensink.h
+++ b/include/kitchensink/kitchensink.h
@@ -1,8 +1,18 @@
#ifndef KITCHENSINK_H
#define KITCHENSINK_H
+/**
+ * @brief Header aggregator
+ *
+ * @file kitchensink.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-27
+ */
+
#include "kitchensink/kitlib.h"
#include "kitchensink/kiterror.h"
+#include "kitchensink/kitformat.h"
+#include "kitchensink/kitcodec.h"
#include "kitchensink/kitsource.h"
#include "kitchensink/kitplayer.h"
#include "kitchensink/kitutils.h"
diff --git a/include/kitchensink/kitcodec.h b/include/kitchensink/kitcodec.h
new file mode 100644
index 0000000..589f84d
--- /dev/null
+++ b/include/kitchensink/kitcodec.h
@@ -0,0 +1,32 @@
+#ifndef KITCODEC_H
+#define KITCODEC_H
+
+/**
+ * @brief Codec type
+ *
+ * @file kitcodec.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KIT_CODEC_NAME_MAX 8
+#define KIT_CODEC_DESC_MAX 48
+
+/**
+ * @brief Contains information about the used codec for playback
+ */
+typedef struct Kit_Codec {
+ unsigned int threads; ///< Currently enabled threads (For all decoders)
+ char name[KIT_CODEC_NAME_MAX]; ///< Codec short name, eg. "ogg" or "webm"
+ char description[KIT_CODEC_DESC_MAX]; ///< Codec longer, more descriptive name
+} Kit_Codec;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITCODEC_H
diff --git a/include/kitchensink/kitconfig.h b/include/kitchensink/kitconfig.h
index e8b2d01..8eda779 100644
--- a/include/kitchensink/kitconfig.h
+++ b/include/kitchensink/kitconfig.h
@@ -1,6 +1,14 @@
#ifndef KITCONFIG_H
#define KITCONFIG_H
+/**
+ * @brief Public API configurations
+ *
+ * @file kitconfig.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
#if defined _WIN32 || defined __CYGWIN__
#define KIT_DLL_IMPORT __declspec(dllimport)
#define KIT_DLL_EXPORT __declspec(dllexport)
diff --git a/include/kitchensink/kiterror.h b/include/kitchensink/kiterror.h
index 824ea8c..e6c2acd 100644
--- a/include/kitchensink/kiterror.h
+++ b/include/kitchensink/kiterror.h
@@ -1,14 +1,38 @@
#ifndef KITERROR_H
#define KITERROR_H
+/**
+ * @brief Error handling functions
+ *
+ * @file kiterror.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
#include "kitchensink/kitconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
+/**
+ * @brief Returns the latest error. This is set by SDL_kitchensink library functions on error.
+ *
+ * @return Error message or NULL
+ */
KIT_API const char* Kit_GetError();
+
+/**
+ * @brief Sets the error message. This should really only be used by the library.
+ *
+ * @param fmt Message format
+ * @param ... Message arguments
+ */
KIT_API void Kit_SetError(const char* fmt, ...);
+
+/**
+ * @brief Clears latest error message. After this, Kit_GetError() will return NULL.
+ */
KIT_API void Kit_ClearError();
#ifdef __cplusplus
diff --git a/include/kitchensink/kitformat.h b/include/kitchensink/kitformat.h
new file mode 100644
index 0000000..3e8df91
--- /dev/null
+++ b/include/kitchensink/kitformat.h
@@ -0,0 +1,33 @@
+#ifndef KITFORMAT_H
+#define KITFORMAT_H
+
+/**
+ * @brief Audio/video output format type
+ *
+ * @file kitformat.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Contains information about the data format coming out from the player
+ */
+typedef struct Kit_OutputFormat {
+ unsigned int format; ///< SDL Format (SDL_PixelFormat if video/subtitle, SDL_AudioFormat if audio)
+ int is_signed; ///< Signedness, 1 = signed, 0 = unsigned (if audio)
+ int bytes; ///< Bytes per sample per channel (if audio)
+ int samplerate; ///< Sampling rate (if audio)
+ int channels; ///< Channels (if audio)
+ int width; ///< Width in pixels (if video)
+ int height; ///< Height in pixels (if video)
+} Kit_OutputFormat;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITFORMAT_H
diff --git a/include/kitchensink/kitlib.h b/include/kitchensink/kitlib.h
index 9a827be..a5fe913 100644
--- a/include/kitchensink/kitlib.h
+++ b/include/kitchensink/kitlib.h
@@ -1,29 +1,118 @@
#ifndef KITLIB_H
#define KITLIB_H
-#include "kitchensink/kiterror.h"
-#include "kitchensink/kitsource.h"
-#include "kitchensink/kitplayer.h"
-#include "kitchensink/kitutils.h"
+/**
+ * @brief Library initialization and deinitialization functionality
+ *
+ * @file kitlib.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
#include "kitchensink/kitconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
+/**
+ * @brief Font hinting options. Used as values for Kit_SetHint(KIT_HINT_FONT_HINTING, ...).
+ */
+enum {
+ KIT_FONT_HINTING_NONE = 0, ///< No hinting. This is recommended option
+ KIT_FONT_HINTING_LIGHT, ///< Light hinting. Use this if you need hinting
+ KIT_FONT_HINTING_NORMAL, ///< Not recommended, please see libass docs for details
+ KIT_FONT_HINTING_NATIVE, ///< Not recommended, please see libass docs for details
+ KIT_FONT_HINTING_COUNT
+};
+
+/**
+ * @brief SDL_kitchensink library version container
+ */
typedef struct Kit_Version {
- unsigned char major;
- unsigned char minor;
- unsigned char patch;
+ unsigned char major; ///< Major version number, raising this signifies API breakage
+ unsigned char minor; ///< Minor version number, small/internal changes
+ unsigned char patch; ///< Patch version number, bugfixes etc.
} Kit_Version;
+/**
+ * @brief Library hint types. Used as keys for Kit_SetHint().
+ *
+ * Note that all of these must be set *before* player initialization for them to take effect!
+ */
+typedef enum Kit_HintType {
+ KIT_HINT_FONT_HINTING, ///< Set font hinting mode (currently used for libass)
+ KIT_HINT_THREAD_COUNT, ///< Set thread count for ffmpeg (1 by default)
+ KIT_HINT_VIDEO_BUFFER_FRAMES, ///< Video output buffer frames (3 by default)
+ KIT_HINT_AUDIO_BUFFER_FRAMES, ///< Audio output buffers (64 by default)
+ KIT_HINT_SUBTITLE_BUFFER_FRAMES ///< Subtitle output buffers (64 by default, used by image subtitles)
+} Kit_HintType;
+
+/**
+ * @brief Library initialization options, please see Kit_Init()
+ *
+ */
enum {
- KIT_INIT_FORMATS = 0x1,
- KIT_INIT_NETWORK = 0x2,
+ KIT_INIT_NETWORK = 0x1, ///< Initialise ffmpeg network support
+ KIT_INIT_ASS = 0x2 ///< Initialize libass support (library must be linked statically or loadable dynamically)
};
+/**
+ * @brief Initialize SDL_kitchensink library.
+ *
+ * This MUST be run before doing anything. After you are done using the library, you should use Kit_Quit() to
+ * deinitialize everything. Otherwise there might be resource leaks.
+ *
+ * Following flags can be used to initialize subsystems:
+ * - `KIT_INIT_NETWORK` for ffmpeg network support (playback from the internet, for example)
+ * - `KIT_INIT_ASS` for libass subtitles (text and ass/ssa subtitle support)
+ *
+ * Note that if this function fails, the failure reason should be available via Kit_GetError().
+ *
+ * For example:
+ * ```
+ * if(Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS) != 0) {
+ * fprintf(stderr, "Error: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * ```
+ *
+ * @param flags Library initialization flags
+ * @return Returns 0 on success, 1 on failure.
+ */
KIT_API int Kit_Init(unsigned int flags);
+
+/**
+ * @brief Deinitializes SDL_kitchensink
+ *
+ * Note that any calls to library functions after this will cause undefined behaviour!
+ */
KIT_API void Kit_Quit();
+
+/**
+ * @brief Sets a librarywide hint
+ *
+ * This can be used to set hints on how the library should behave. See Kit_HintType
+ * for all the options.
+ *
+ * @param type Hint type (refer to Kit_HintType for options)
+ * @param value Value for the hint
+ */
+KIT_API void Kit_SetHint(Kit_HintType type, int value);
+
+/**
+ * @brief Gets a previously set or default hint value
+ *
+ * @param type Hint type (refer to Kit_HintType for options)
+ * @return Hint value
+ */
+KIT_API int Kit_GetHint(Kit_HintType type);
+
+/**
+ * @brief Can be used to fetch the version of the linked SDL_kitchensink library
+ *
+ * @param version Allocated Kit_Version
+ */
KIT_API void Kit_GetVersion(Kit_Version *version);
#ifdef __cplusplus
diff --git a/include/kitchensink/kitplayer.h b/include/kitchensink/kitplayer.h
index b9ea1c7..1c73020 100644
--- a/include/kitchensink/kitplayer.h
+++ b/include/kitchensink/kitplayer.h
@@ -1,124 +1,339 @@
#ifndef KITPLAYER_H
#define KITPLAYER_H
+/**
+ * @brief Video/audio player functions
+ *
+ * @file kitplayer.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-27
+ */
+
#include "kitchensink/kitsource.h"
#include "kitchensink/kitconfig.h"
+#include "kitchensink/kitformat.h"
+#include "kitchensink/kitcodec.h"
-#include <SDL2/SDL_render.h>
-#include <SDL2/SDL_thread.h>
-#include <SDL2/SDL_surface.h>
-
-#include <stdbool.h>
+#include <SDL_render.h>
#ifdef __cplusplus
extern "C" {
#endif
-#define KIT_CODECMAX 16
-#define KIT_CODECNAMEMAX 128
-
+/**
+ * @brief Playback states
+ */
typedef enum Kit_PlayerState {
KIT_STOPPED = 0, ///< Playback stopped or has not started yet.
- KIT_PLAYING, ///< Playback started & player is actively decoding.
- KIT_PAUSED, ///< Playback paused; player is actively decoding but no new data is given out.
- KIT_CLOSED ///< Playback is stopped and player is closing.
+ KIT_PLAYING, ///< Playback started & player is actively decoding.
+ KIT_PAUSED, ///< Playback paused; player is actively decoding but no new data is given out.
+ KIT_CLOSED, ///< Playback is stopped and player is closing.
} Kit_PlayerState;
-typedef struct Kit_AudioFormat {
- int stream_idx; ///< Stream index
- bool is_enabled; ///< Is stream enabled
- unsigned int format; ///< SDL Audio Format
- bool is_signed; ///< Signedness
- int bytes; ///< Bytes per sample per channel
- int samplerate; ///< Sampling rate
- int channels; ///< Channels
-} Kit_AudioFormat;
-
-typedef struct Kit_VideoFormat {
- int stream_idx; ///< Stream index
- bool is_enabled; ///< Is stream enabled
- unsigned int format; ///< SDL Pixel Format
- int width; ///< Width in pixels
- int height; ///< Height in pixels
-} Kit_VideoFormat;
-
-typedef struct Kit_SubtitleFormat {
- int stream_idx; ///< Stream index
- bool is_enabled; ///< Is stream enabled
-} Kit_SubtitleFormat;
-
+/**
+ * @brief Player state container
+ */
typedef struct Kit_Player {
- // Local state
- Kit_PlayerState state; ///< Playback state
- Kit_VideoFormat vformat; ///< Video format information
- Kit_AudioFormat aformat; ///< Audio format information
- Kit_SubtitleFormat sformat; ///< Subtitle format information
-
- // Synchronization
- double clock_sync; ///< Clock sync point
- double pause_start; ///< Timestamp of pause beginning
- double vclock_pos; ///< Video stream last pts
-
- // Threading
- SDL_Thread *dec_thread; ///< Decoder thread
- SDL_mutex *vmutex; ///< Video stream buffer lock
- SDL_mutex *amutex; ///< Audio stream buffer lock
- SDL_mutex *smutex; ///< Subtitle stream buffer lock
- SDL_mutex *cmutex; ///< Control stream buffer lock
-
- // Buffers
- void *abuffer; ///< Audio stream buffer
- void *vbuffer; ///< Video stream buffer
- void *sbuffer; ///< Subtitle stream buffer
- void *cbuffer; ///< Control stream buffer
-
- // FFmpeg internal state
- void *vcodec_ctx; ///< FFmpeg: Video codec context
- void *acodec_ctx; ///< FFmpeg: Audio codec context
- void *scodec_ctx; ///< FFmpeg: Subtitle codec context
- void *tmp_vframe; ///< FFmpeg: Preallocated temporary video frame
- void *tmp_aframe; ///< FFmpeg: Preallocated temporary audio frame
- void *tmp_sframe; ///< FFmpeg: Preallocated temporary subtitle frame
- void *swr; ///< FFmpeg: Audio resampler
- void *sws; ///< FFmpeg: Video converter
-
- // libass
- void *ass_renderer;
- void *ass_track;
-
- // Other
- uint8_t seek_flag;
- const Kit_Source *src; ///< Reference to Audio/Video source
+ Kit_PlayerState state; ///< Playback state
+ void *decoders[3]; ///< Decoder contexts
+ void *dec_thread; ///< Decoder thread
+ void *dec_lock; ///< Decoder lock
+ const Kit_Source *src; ///< Reference to Audio/Video source
+ double pause_started; ///< Temporary flag for handling pauses
} Kit_Player;
+/**
+ * @brief Contains data about a stream selected for playback
+ */
+typedef struct Kit_PlayerStreamInfo {
+ Kit_Codec codec; ///< Decoder codec information
+ Kit_OutputFormat output; ///< Information about the output format
+} Kit_PlayerStreamInfo;
+
+/**
+ * @brief Contains information about the streams selected for playback
+ *
+ */
typedef struct Kit_PlayerInfo {
- char acodec[KIT_CODECMAX]; ///< Audio codec short name, eg "ogg", "mp3"
- char acodec_name[KIT_CODECNAMEMAX]; ///< Audio codec long, more descriptive name
- char vcodec[KIT_CODECMAX]; ///< Video codec short name, eg. "x264"
- char vcodec_name[KIT_CODECNAMEMAX]; ///< Video codec long, more descriptive name
- char scodec[KIT_CODECMAX]; ///< Subtitle codec short name, eg. "ass"
- char scodec_name[KIT_CODECNAMEMAX]; ///< Subtitle codec long, more descriptive name
- Kit_VideoFormat video; ///< Video format information
- Kit_AudioFormat audio; ///< Audio format information
- Kit_SubtitleFormat subtitle; ///< Subtitle format information
+ Kit_PlayerStreamInfo video; ///< Video stream data
+ Kit_PlayerStreamInfo audio; ///< Audio stream data
+ Kit_PlayerStreamInfo subtitle; ///< Subtitle stream data
} Kit_PlayerInfo;
-KIT_API Kit_Player* Kit_CreatePlayer(const Kit_Source *src);
+/**
+ * @brief Creates a new player from a source.
+ *
+ * Creates a new player from the given source. The source must be previously succesfully
+ * initialized by calling either Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom(),
+ * and it must not be used by any other player. Source must stay valid during the whole
+ * playback (as in, don't close it while stuff is playing).
+ *
+ * Screen width and height are used for subtitle positioning, scaling and rendering resolution.
+ * Ideally this should be precisely the size of your screen surface (in pixels).
+ * Higher resolution leads to higher resolution text rendering. This MUST be set precisely
+ * if you plan to use font hinting! If you don't care or don't have subtitles at all,
+ * set both to video surface size or 0.
+ *
+ * For streams, either video and/or audio stream MUST be set! Either set the stream indexes manually,
+ * or pick them automatically by using Kit_GetBestSourceStream().
+ *
+ * On success, this will return an initialized Kit_Player which can later be freed by Kit_ClosePlayer().
+ * On error, NULL is returned and a more detailed error is availably via Kit_GetError().
+ *
+ * For example:
+ * ```
+ * Kit_Player *player = Kit_CreatePlayer(
+ * src,
+ * Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO),
+ * Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO),
+ * Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE),
+ * 1280, 720);
+ * if(player == NULL) {
+ * fprintf(stderr, "Unable to create player: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * ```
+ *
+ * @param src Valid video/audio source
+ * @param video_stream_index Video stream index or -1 if not wanted
+ * @param audio_stream_index Audio stream index or -1 if not wanted
+ * @param subtitle_stream_index Subtitle stream index or -1 if not wanted
+ * @param screen_w Screen width in pixels
+ * @param screen_h Screen height in pixels
+ * @return Ínitialized Kit_Player or NULL
+ */
+KIT_API Kit_Player* Kit_CreatePlayer(const Kit_Source *src,
+ int video_stream_index,
+ int audio_stream_index,
+ int subtitle_stream_index,
+ int screen_w,
+ int screen_h);
+
+/**
+ * @brief Close previously initialized player
+ *
+ * Closes a previously initialized Kit_Player instance. Note that this does NOT free
+ * the linked Kit_Source -- you must free it manually.
+ *
+ * @param player Player instance
+ */
KIT_API void Kit_ClosePlayer(Kit_Player *player);
-KIT_API int Kit_UpdatePlayer(Kit_Player *player);
-KIT_API int Kit_GetVideoData(Kit_Player *player, SDL_Texture *texture);
-KIT_API int Kit_GetSubtitleData(Kit_Player *player, SDL_Renderer *renderer);
-KIT_API int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int cur_buf_len);
+/**
+ * @brief Sets the current screen size in pixels
+ *
+ * Call this to change the subtitle font rendering resolution if eg. your
+ * video window size changes.
+ *
+ * This does nothing if subtitles are not in use or if subtitles are bitmaps.
+ *
+ * @param player Player instance
+ * @param w New width in pixels
+ * @param h New height in pixels
+ */
+KIT_API void Kit_SetPlayerScreenSize(Kit_Player *player, int w, int h);
+
+/**
+ * @brief Gets the current video stream index
+ *
+ * Returns the current video stream index or -1 if one is not selected.
+ *
+ * @param player Player instance
+ * @return Video stream index or -1
+ */
+KIT_API int Kit_GetPlayerVideoStream(const Kit_Player *player);
+
+/**
+ * @brief Gets the current audio stream index
+ *
+ * Returns the current audio stream index or -1 if one is not selected.
+ *
+ * @param player Player instance
+ * @return Audio stream index or -1
+ */
+KIT_API int Kit_GetPlayerAudioStream(const Kit_Player *player);
+
+/**
+ * @brief Gets the current subtitle stream index
+ *
+ * Returns the current subtitle stream index or -1 if one is not selected.
+ *
+ * @param player Player instance
+ * @return Subtitle stream index or -1
+ */
+KIT_API int Kit_GetPlayerSubtitleStream(const Kit_Player *player);
+
+/**
+ * @brief Fetches a new video frame from the player
+ *
+ * Note that the output texture must be previously allocated and valid.
+ *
+ * It is important to select the correct texture format and size. If you pick a different
+ * texture format or size from what the decoder outputs, then the decoder will attempt to convert
+ * the frames to fit the texture. This will slow down the decoder a *lot* however, so if possible,
+ * pick the texture format from what Kit_GetPlayerInfo() outputs.
+ *
+ * Access flag for the texture *MUST* always be SDL_TEXTUREACCESS_STATIC! Anything else will lead to
+ * undefined behaviour.
+ *
+ * This function will do nothing if player playback has not been started.
+ *
+ * @param player Player instance
+ * @param texture A previously allocated texture
+ * @return 0 on success, 1 on error
+ */
+KIT_API int Kit_GetPlayerVideoData(Kit_Player *player, SDL_Texture *texture);
+
+/**
+ * @brief Fetches subtitle data from the player
+ *
+ * Output texture will be used as a texture atlas for the subtitle fragments.
+ *
+ * Note that the output texture must be previously allocated and valid. Make sure to have large
+ * enough a texture for the rendering resolution you picked! If your rendering resolution if 4k,
+ * then make sure to have texture sized 4096x4096 etc. This gives the texture room to handle the
+ * worst case subtitle textures. If your resolutions is too small, this function will return
+ * value -1. At that point you can replace your current texture with a bigger one on the fly.
+ *
+ * Note that the texture format for the atlas texture *MUST* be SDL_PIXELFORMAT_RGBA32 and
+ * the access flag *MUST* be set to SDL_TEXTUREACCESS_STATIC for correct rendering.
+ * Using any other format will lead to undefined behaviour. Also, make sure to set scaling quality
+ * to 0 or "nearest" before creating the texture -- otherwise you get artifacts
+ * (see SDL_HINT_RENDER_SCALE_QUALITY).
+ *
+ * This function will do nothing if player playback has not been started.
+ *
+ * For example:
+ * ```
+ * SDL_Rect sources[256];
+ * SDL_Rect targets[256];
+ * int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, 256);
+ * for(int i = 0; i < got; i++) {
+ * SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]);
+ * }
+ * ```
+ *
+ * @param player Player instance
+ * @param texture A previously allocated texture
+ * @param sources List of source rectangles to copy fropm
+ * @param targets List of target rectangles to render
+ * @param limit Defines the maximum size of your rectangle lists
+ * @return Number of sources or <0 on error
+ */
+KIT_API int Kit_GetPlayerSubtitleData(Kit_Player *player,
+ SDL_Texture *texture,
+ SDL_Rect *sources,
+ SDL_Rect *targets,
+ int limit);
+
+/**
+ * @brief Fetches audio data from the player
+ *
+ * Note that the output buffer must be previously allocated.
+ *
+ * Outputted audio data will be precisely what is described by the output format struct given
+ * by Kit_GetPlayerInfo().
+ *
+ * This function will attemt to read the maximum allowed amount of data allowed by the length
+ * argument. It is possible however that there is not enough data available, at which point
+ * this function will read less and return value may differ from maximum allowed value.
+ * Return value 0 should be taken as a hint that there is nothing available.
+ *
+ * This function will do nothing if player playback has not been started.
+ *
+ * @param player Player instance
+ * @param buffer Buffer to read into
+ * @param length Maximum length of the buffer
+ * @return Amount of data that was read, <0 on error.
+ */
+KIT_API int Kit_GetPlayerAudioData(Kit_Player *player, unsigned char *buffer, int length);
+
+/**
+ * @brief Fetches information about the currently selected streams
+ *
+ * This function should be used to fetch codec information and output format data from the player
+ * before creating textures and setting up audio outputs.
+ *
+ * @param player Player instance
+ * @param info A previously allocated Kit_PlayerInfo instance
+ */
KIT_API void Kit_GetPlayerInfo(const Kit_Player *player, Kit_PlayerInfo *info);
+/**
+ * @brief Returns the current state of the player
+ *
+ * @param player Player instance
+ * @return Current state of the player, see Kit_PlayerState
+ */
KIT_API Kit_PlayerState Kit_GetPlayerState(const Kit_Player *player);
+
+/**
+ * @brief Starts playback
+ *
+ * State shifts:
+ * - If player is already playing, will do nothing.
+ * - If player is paused, will resume playback.
+ * - If player is stopped, will begin playback (and background decoding).
+ *
+ * @param player Player instance
+ */
KIT_API void Kit_PlayerPlay(Kit_Player *player);
+
+/**
+ * @brief Stops playback
+ *
+ * State shifts:
+ * - If player is already stopped, will do nothing.
+ * - If player is paused, will stop playback.
+ * - If player is started, will stop playback (and background decoding).
+ *
+ * @param player Player instance
+ */
KIT_API void Kit_PlayerStop(Kit_Player *player);
+
+/**
+ * @brief Pauses playback
+ *
+ * State shifts:
+ * - If player is already paused, will do nothing.
+ * - If player is stopped, will do nothing.
+ * - If player is started, will pause playback (and background decoding).
+ *
+ * @param player Player instance
+ */
KIT_API void Kit_PlayerPause(Kit_Player *player);
+/**
+ * @brief Seek to timestamp
+ *
+ * Rewinds or forwards video/audio playback to the given timestamp (in seconds).
+ *
+ * This may not work for network or custom sources!
+ *
+ * @param player Player instance
+ * @param time Timestamp to seek to in seconds
+ * @return 0 on success, 1 on failure.
+ */
KIT_API int Kit_PlayerSeek(Kit_Player *player, double time);
+
+/**
+ * @brief Get the duration of the source
+ *
+ * Returns the duration of the source in seconds
+ *
+ * @param player Player instance
+ * @return Duration
+ */
KIT_API double Kit_GetPlayerDuration(const Kit_Player *player);
+
+/**
+ * @brief Get the current position of the playback
+ *
+ * Returns the position of the playback in seconds
+ *
+ * @param player Player instance
+ * @return Position
+ */
KIT_API double Kit_GetPlayerPosition(const Kit_Player *player);
#ifdef __cplusplus
diff --git a/include/kitchensink/kitsource.h b/include/kitchensink/kitsource.h
index ed1711f..9b744ca 100644
--- a/include/kitchensink/kitsource.h
+++ b/include/kitchensink/kitsource.h
@@ -1,15 +1,27 @@
#ifndef KITSOURCE_H
#define KITSOURCE_H
+/**
+ * @brief Video/Audio source file handling
+ *
+ * @file kitsource.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-27
+ */
+
+#include <inttypes.h>
+#include <SDL_rwops.h>
#include "kitchensink/kitconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
-#define KIT_CODECNAMESIZE 32
-#define KIT_CODECLONGNAMESIZE 128
-
+/**
+ * @brief Type of the stream.
+ *
+ * This is used by Kit_SourceStreamInfo and Kit_GetSourceStreamInfo().
+ */
typedef enum Kit_StreamType {
KIT_STREAMTYPE_UNKNOWN, ///< Unknown stream type
KIT_STREAMTYPE_VIDEO, ///< Video stream
@@ -19,26 +31,202 @@ typedef enum Kit_StreamType {
KIT_STREAMTYPE_ATTACHMENT ///< Attachment stream (images, etc)
} Kit_StreamType;
+/**
+ * @brief Audio/video source.
+ *
+ * Should be created using Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom(), and
+ * closed with Kit_CloseSource().
+ *
+ * Source must exist for the whole duration of using a player. You must take care of closing the source
+ * yourself after you are done with it!
+ */
typedef struct Kit_Source {
- int astream_idx; ///< Audio stream index
- int vstream_idx; ///< Video stream index
- int sstream_idx; ///< Subtitle stream index
void *format_ctx; ///< FFmpeg: Videostream format context
+ void *avio_ctx; ///< FFmpeg: AVIO context
} Kit_Source;
-typedef struct Kit_Stream {
+/**
+ * @brief Information for a source stream.
+ *
+ * Fetch information by using Kit_GetSourceStreamInfo().
+ */
+typedef struct Kit_SourceStreamInfo {
int index; ///< Stream index
Kit_StreamType type; ///< Stream type
-} Kit_StreamInfo;
+} Kit_SourceStreamInfo;
+
+/**
+ * @brief Callback function type for reading data stream
+ *
+ * Used by Kit_CreateSourceFromCustom() for reading data from user defined source.
+ *
+ * A custom reader function must accept three arguments:
+ * - userdata, this is the same data as set as last argument for Kit_CreateSourceFromCustom
+ * - buf, a buffer the data must be copied into
+ * - size, how much data you are expected to provide at maximum.
+ *
+ * The function must return the amount of bytes copied to the buffer or <0 on error.
+ *
+ * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation
+ * for any further details.
+ */
+typedef int (*Kit_ReadCallback)(void *userdata, uint8_t *buf, int size);
+
+/**
+ * @brief Callback function type for seeking data strema
+ *
+ * Used by Kit_CreateSourceFromCustom() for seeking a user defined source.
+ *
+ * A custom seeking function must accept three arguments:
+ * - userdata, this is the same data as set as last argument for Kit_CreateSourceFromCustom
+ * - offset, an seeking offset in bytes
+ * - whence, reference position for the offset.
+ *
+ * Whence parameter can be one of the standard fseek values or optionally AVSEEK_SIZE.
+ * - SEEK_SET: Reference position is beginning of file
+ * - SEEK_CUR: Reference position is the current position of the file pointer
+ * - SEEK_END: Reference position is the end of the file
+ * - AVSEEK_SIZE: Optional. Does not seek, instead finds the size of the source file.
+ * - AVSEEK_FORCE: Optional. Suggests that seeking should be done at any cost. May be passed alongside
+ * any of the SEEK_* flags, eg. SEEK_SET|AVSEEK_FORCE.
+ *
+ * The function must return the position (in bytes) we seeked to or <0 on error or on unsupported operation.
+ *
+ * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation
+ * for any further details.
+ */
+typedef int64_t (*Kit_SeekCallback)(void *userdata, int64_t offset, int whence);
+
+/**
+ * @brief Create a new source from a given url
+ *
+ * This can be used to load video/audio from a file or network resource. If you wish to
+ * use network resources, make sure the library has been initialized using KIT_INIT_NETWORK flag.
+ *
+ * This function will return an initialized Kit_Source on success. Note that you need to manually
+ * free the source when it's no longer needed by calling Kit_CloseSource().
+ *
+ * On failure, this function will return NULL, and further error data is available via Kit_GetError().
+ *
+ * For example:
+ * ```
+ * if(Kit_CreateSourceFromUrl(filename) == NULL) {
+ * fprintf(stderr, "Error: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * ```
+ *
+ * @param url File path or URL to a video/audio resource
+ * @return Returns an initialized Kit_Source* on success or NULL on failure
+ */
+KIT_API Kit_Source* Kit_CreateSourceFromUrl(const char *url);
-KIT_API Kit_Source* Kit_CreateSourceFromUrl(const char *path);
+/**
+ * @brief Create a new source from custom data
+ *
+ * This can be used to load data from any resource via the given read and seek functions.
+ *
+ * This function will return an initialized Kit_Source on success. Note that you need to manually
+ * free the source when it's no longer needed by calling Kit_CloseSource().
+ *
+ * On failure, this function will return NULL, and further error data is available via Kit_GetError().
+ *
+ * For example:
+ * ```
+ * if(Kit_CreateSourceFromCustom(read_fn, seek_fn, fp) == NULL) {
+ * fprintf(stderr, "Error: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * ```
+ *
+ * @param read_cb Read function callback
+ * @param seek_cb Seek function callback
+ * @param userdata Any data (or NULL). Will be passed to read_cb and/or seek_cb functions as-is.
+ * @return Returns an initialized Kit_Source* on success or NULL on failure
+ */
+KIT_API Kit_Source* Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallback seek_cb, void *userdata);
+
+/**
+ * @brief Create a new source from SDL RWops struct
+ *
+ * Can be used to read data from SDL compatible sources.
+ *
+ * This function will return an initialized Kit_Source on success. Note that you need to manually
+ * free the source when it's no longer needed by calling Kit_CloseSource().
+ *
+ * On failure, this function will return NULL, and further error data is available via Kit_GetError().
+ *
+ * Note that the RWops struct must exist during the whole lifetime of the source, and you must take
+ * care of freeing the rwops after it's no longer needed.
+ *
+ * For example:
+ * ```
+ * SDL_RWops *rw = SDL_RWFromFile("myvideo.mkv", "rb");
+ * if(Kit_CreateSourceFromRW(rw) == NULL) {
+ * fprintf(stderr, "Error: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * ```
+ *
+ * @param rw_ops Initialized RWOps
+ * @return KIT_API* Kit_CreateSourceFromRW
+ */
+KIT_API Kit_Source* Kit_CreateSourceFromRW(SDL_RWops *rw_ops);
+
+/**
+ * @brief Closes a previously initialized source
+ *
+ * Closes a Kit_Source that was previously created by Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom()
+ * and frees up all memory and resources used by it. Using the source for anything after this will
+ * lead to undefined behaviour.
+ *
+ * Passing NULL as argument is valid, and will do nothing.
+ *
+ * @param src Previously initialized Kit_Source to close
+ */
KIT_API void Kit_CloseSource(Kit_Source *src);
-KIT_API int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_StreamInfo *info, int index);
+/**
+ * @brief Fetches stream information for a given stream index
+ *
+ * Sets fields for given Kit_SourceStreamInfo with information about the stream.
+ *
+ * For example:
+ * ```
+ * Kit_SourceStreamInfo stream;
+ * if(Kit_GetSourceStreamInfo(source, &stream, 0) == 1) {
+ * fprintf(stderr, "Error: %s\n", Kit_GetError());
+ * return 1;
+ * }
+ * fprintf(stderr, "Stream type: %s\n", Kit_GetKitStreamTypeString(stream.type))
+ * ```
+ *
+ * @param src Source to query from
+ * @param info A previously allocated Kit_SourceStreamInfo to fill out
+ * @param index Stream index (starting from 0)
+ * @return 0 on success, 1 on error.
+ */
+KIT_API int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_SourceStreamInfo *info, int index);
+
+/**
+ * @brief Gets the amount of streams in source
+ *
+ * @param src Source to query from
+ * @return Number of streams in the source
+ */
KIT_API int Kit_GetSourceStreamCount(const Kit_Source *src);
+
+/**
+ * @brief Gets the best stream index for a given stream type.
+ *
+ * Find the best stream index for a given stream type, if one exists. If there is no
+ * stream for the wanted type, will return -1.
+ *
+ * @param src Source to query from
+ * @param type Stream type
+ * @return Index number on success (>=0), -1 on error.
+ */
KIT_API int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_StreamType type);
-KIT_API int Kit_SetSourceStream(Kit_Source *src, const Kit_StreamType type, int index);
-KIT_API int Kit_GetSourceStream(const Kit_Source *src, const Kit_StreamType type);
#ifdef __cplusplus
}
diff --git a/include/kitchensink/kitutils.h b/include/kitchensink/kitutils.h
index af3307c..af0e14b 100644
--- a/include/kitchensink/kitutils.h
+++ b/include/kitchensink/kitutils.h
@@ -1,14 +1,42 @@
#ifndef KITUTILS_H
#define KITUTILS_H
+/**
+ * @brief Helpful utilities
+ *
+ * @file kitutils.h
+ * @author Tuomas Virtanen
+ * @date 2018-06-25
+ */
+
#include "kitchensink/kitconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
+/**
+ * @brief Returns a descriptive string for SDL audio format types
+ *
+ * @param type SDL_AudioFormat
+ * @return Format string, eg. "AUDIO_S8".
+ */
KIT_API const char* Kit_GetSDLAudioFormatString(unsigned int type);
+
+/**
+ * @brief Returns a descriptive string for SDL pixel format types
+ *
+ * @param type SDL_PixelFormat
+ * @return Format string, eg. "SDL_PIXELFORMAT_YV12"
+ */
KIT_API const char* Kit_GetSDLPixelFormatString(unsigned int type);
+
+/**
+ * @brief Returns a descriptibe string for Kitchensink stream types
+ *
+ * @param type Kit_StreamType
+ * @return Format string, eg. "KIT_STREAMTYPE_VIDEO"
+ */
KIT_API const char* Kit_GetKitStreamTypeString(unsigned int type);
#ifdef __cplusplus
diff --git a/pkg-config.pc.in b/pkg-config.pc.in
new file mode 100644
index 0000000..53e3a53
--- /dev/null
+++ b/pkg-config.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=@CMAKE_INSTALL_FULL_LIBDIR@
+includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+
+Name: SDL_kitchensink
+Description: SDL2/ffmpeg video playback library
+Version: @KIT_VERSION@
+URL: https://github.com/katajakasa/SDL_kitchensink
+
+Libs: -L${libdir} -l@PROJECT_NAME@
+Cflags: -I${includedir}
diff --git a/sonar-project.properties b/sonar-project.properties
new file mode 100644
index 0000000..3edb3a5
--- /dev/null
+++ b/sonar-project.properties
@@ -0,0 +1,7 @@
+sonar.projectKey=sdl_kitchensink
+sonar.projectName=SDL_Kitchensink
+sonar.projectVersion=1.0
+sonar.sourceEncoding=UTF-8
+sonar.sources=src,include
+sonar.language=c
+sonar.cfamily.build-wrapper-output=bw-output
diff --git a/src/internal/audio/kitaudio.c b/src/internal/audio/kitaudio.c
new file mode 100644
index 0000000..b1a0fdc
--- /dev/null
+++ b/src/internal/audio/kitaudio.c
@@ -0,0 +1,321 @@
+#include <assert.h>
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <libavformat/avformat.h>
+#include <libavutil/samplefmt.h>
+#include <libswresample/swresample.h>
+#include <SDL.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitbuffer.h"
+#include "kitchensink/internal/audio/kitaudio.h"
+#include "kitchensink/internal/utils/kitringbuffer.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+#define AUDIO_SYNC_THRESHOLD 0.05
+
+typedef struct Kit_AudioDecoder {
+ SwrContext *swr;
+ AVFrame *scratch_frame;
+} Kit_AudioDecoder;
+
+typedef struct Kit_AudioPacket {
+ double pts;
+ size_t original_size;
+ Kit_RingBuffer *rb;
+} Kit_AudioPacket;
+
+
+Kit_AudioPacket* _CreateAudioPacket(const char* data, size_t len, double pts) {
+ Kit_AudioPacket *p = calloc(1, sizeof(Kit_AudioPacket));
+ p->rb = Kit_CreateRingBuffer(len);
+ Kit_WriteRingBuffer(p->rb, data, len);
+ p->pts = pts;
+ return p;
+}
+
+enum AVSampleFormat _FindAVSampleFormat(int format) {
+ switch(format) {
+ case AUDIO_U8: return AV_SAMPLE_FMT_U8;
+ case AUDIO_S16SYS: return AV_SAMPLE_FMT_S16;
+ case AUDIO_S32SYS: return AV_SAMPLE_FMT_S32;
+ default: return AV_SAMPLE_FMT_NONE;
+ }
+}
+
+int64_t _FindAVChannelLayout(int channels) {
+ switch(channels) {
+ case 1: return AV_CH_LAYOUT_MONO;
+ case 2: return AV_CH_LAYOUT_STEREO;
+ default: return AV_CH_LAYOUT_STEREO_DOWNMIX;
+ }
+}
+
+int _FindChannelLayout(uint64_t channel_layout) {
+ switch(channel_layout) {
+ case AV_CH_LAYOUT_MONO: return 1;
+ case AV_CH_LAYOUT_STEREO: return 2;
+ default: return 2;
+ }
+}
+
+int _FindBytes(enum AVSampleFormat fmt) {
+ switch(fmt) {
+ case AV_SAMPLE_FMT_U8P:
+ case AV_SAMPLE_FMT_U8:
+ return 1;
+ case AV_SAMPLE_FMT_S32P:
+ case AV_SAMPLE_FMT_S32:
+ return 4;
+ default:
+ return 2;
+ }
+}
+
+int _FindSDLSampleFormat(enum AVSampleFormat fmt) {
+ switch(fmt) {
+ case AV_SAMPLE_FMT_U8P:
+ case AV_SAMPLE_FMT_U8:
+ return AUDIO_U8;
+ case AV_SAMPLE_FMT_S32P:
+ case AV_SAMPLE_FMT_S32:
+ return AUDIO_S32SYS;
+ default:
+ return AUDIO_S16SYS;
+ }
+}
+
+int _FindSignedness(enum AVSampleFormat fmt) {
+ switch(fmt) {
+ case AV_SAMPLE_FMT_U8P:
+ case AV_SAMPLE_FMT_U8:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static void free_out_audio_packet_cb(void *packet) {
+ Kit_AudioPacket *p = packet;
+ Kit_DestroyRingBuffer(p->rb);
+ free(p);
+}
+
+static void dec_decode_audio_cb(Kit_Decoder *dec, AVPacket *in_packet) {
+ assert(dec != NULL);
+ assert(in_packet != NULL);
+
+ Kit_AudioDecoder *audio_dec = dec->userdata;
+ int frame_finished;
+ int len;
+ int len2;
+ int dst_linesize;
+ int dst_nb_samples;
+ int dst_bufsize;
+ double pts;
+ unsigned char **dst_data;
+ Kit_AudioPacket *out_packet;
+
+ // Decode as long as there is data
+ while(in_packet->size > 0) {
+ len = avcodec_decode_audio4(dec->codec_ctx, audio_dec->scratch_frame, &frame_finished, in_packet);
+ if(len < 0) {
+ return;
+ }
+
+ if(frame_finished) {
+ dst_nb_samples = av_rescale_rnd(
+ audio_dec->scratch_frame->nb_samples,
+ dec->output.samplerate, // Target samplerate
+ dec->codec_ctx->sample_rate, // Source samplerate
+ AV_ROUND_UP);
+
+ av_samples_alloc_array_and_samples(
+ &dst_data,
+ &dst_linesize,
+ dec->output.channels,
+ dst_nb_samples,
+ _FindAVSampleFormat(dec->output.format),
+ 0);
+
+ len2 = swr_convert(
+ audio_dec->swr,
+ dst_data,
+ audio_dec->scratch_frame->nb_samples,
+ (const unsigned char **)audio_dec->scratch_frame->extended_data,
+ audio_dec->scratch_frame->nb_samples);
+
+ dst_bufsize = av_samples_get_buffer_size(
+ &dst_linesize,
+ dec->output.channels,
+ len2,
+ _FindAVSampleFormat(dec->output.format), 1);
+
+ // Get presentation timestamp
+#ifndef FF_API_FRAME_GET_SET
+ pts = av_frame_get_best_effort_timestamp(audio_dec->scratch_frame);
+#else
+ pts = audio_dec->scratch_frame->best_effort_timestamp;
+#endif
+ pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base);
+
+ // Lock, write to audio buffer, unlock
+ out_packet = _CreateAudioPacket(
+ (char*)dst_data[0], (size_t)dst_bufsize, pts);
+ Kit_WriteDecoderOutput(dec, out_packet);
+
+ // Free temps
+ av_freep(&dst_data[0]);
+ av_freep(&dst_data);
+ }
+
+ in_packet->size -= len;
+ in_packet->data += len;
+ }
+}
+
+static void dec_close_audio_cb(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+
+ Kit_AudioDecoder *audio_dec = dec->userdata;
+ if(audio_dec->scratch_frame != NULL) {
+ av_frame_free(&audio_dec->scratch_frame);
+ }
+ if(audio_dec->swr != NULL) {
+ swr_free(&audio_dec->swr);
+ }
+ free(audio_dec);
+}
+
+Kit_Decoder* Kit_CreateAudioDecoder(const Kit_Source *src, int stream_index) {
+ assert(src != NULL);
+ if(stream_index < 0) {
+ return NULL;
+ }
+
+ Kit_LibraryState *state = Kit_GetLibraryState();
+
+ // First the generic decoder component ...
+ Kit_Decoder *dec = Kit_CreateDecoder(
+ src,
+ stream_index,
+ state->audio_buf_frames,
+ free_out_audio_packet_cb,
+ state->thread_count);
+ if(dec == NULL) {
+ goto exit_0;
+ }
+
+ // ... then allocate the audio decoder
+ Kit_AudioDecoder *audio_dec = calloc(1, sizeof(Kit_AudioDecoder));
+ if(audio_dec == NULL) {
+ goto exit_1;
+ }
+
+ // Create temporary audio frame
+ audio_dec->scratch_frame = av_frame_alloc();
+ if(audio_dec->scratch_frame == NULL) {
+ Kit_SetError("Unable to initialize temporary audio frame");
+ goto exit_2;
+ }
+
+ // Set format configs
+ Kit_OutputFormat output;
+ memset(&output, 0, sizeof(Kit_OutputFormat));
+ output.samplerate = dec->codec_ctx->sample_rate;
+ output.channels = _FindChannelLayout(dec->codec_ctx->channel_layout);
+ output.bytes = _FindBytes(dec->codec_ctx->sample_fmt);
+ output.is_signed = _FindSignedness(dec->codec_ctx->sample_fmt);
+ output.format = _FindSDLSampleFormat(dec->codec_ctx->sample_fmt);
+
+ // Create resampler
+ audio_dec->swr = swr_alloc_set_opts(
+ NULL,
+ _FindAVChannelLayout(output.channels), // Target channel layout
+ _FindAVSampleFormat(output.format), // Target fmt
+ output.samplerate, // Target samplerate
+ dec->codec_ctx->channel_layout, // Source channel layout
+ dec->codec_ctx->sample_fmt, // Source fmt
+ dec->codec_ctx->sample_rate, // Source samplerate
+ 0, NULL);
+
+ if(swr_init(audio_dec->swr) != 0) {
+ Kit_SetError("Unable to initialize audio resampler context");
+ goto exit_3;
+ }
+
+ // Set callbacks and userdata, and we're go
+ dec->dec_decode = dec_decode_audio_cb;
+ dec->dec_close = dec_close_audio_cb;
+ dec->userdata = audio_dec;
+ dec->output = output;
+ return dec;
+
+exit_3:
+ av_frame_free(&audio_dec->scratch_frame);
+exit_2:
+ free(audio_dec);
+exit_1:
+ Kit_CloseDecoder(dec);
+exit_0:
+ return NULL;
+}
+
+int Kit_GetAudioDecoderData(Kit_Decoder *dec, unsigned char *buf, int len) {
+ assert(dec != NULL);
+
+ Kit_AudioPacket *packet = Kit_PeekDecoderOutput(dec);
+ if(packet == NULL) {
+ return 0;
+ }
+
+ int ret = 0;
+ int bytes_per_sample = dec->output.bytes * dec->output.channels;
+ double bytes_per_second = bytes_per_sample * dec->output.samplerate;
+ double sync_ts = _GetSystemTime() - dec->clock_sync;
+
+ if(packet->pts > sync_ts + AUDIO_SYNC_THRESHOLD) {
+ return 0;
+ } else if(packet->pts < sync_ts - AUDIO_SYNC_THRESHOLD) {
+ // Audio is lagging, skip until good pts is found
+ while(1) {
+ Kit_AdvanceDecoderOutput(dec);
+ free_out_audio_packet_cb(packet);
+ packet = Kit_PeekDecoderOutput(dec);
+ if(packet == NULL) {
+ break;
+ } else {
+ dec->clock_pos = packet->pts;
+ }
+ if(packet->pts > sync_ts - AUDIO_SYNC_THRESHOLD) {
+ break;
+ }
+ }
+ }
+
+ // If we have no viable packet, just skip
+ if(packet == NULL) {
+ return 0;
+ }
+
+ // Read data from packet ringbuffer
+ if(len > 0) {
+ ret = Kit_ReadRingBuffer(packet->rb, (char*)buf, len);
+ }
+
+ // If ringbuffer is cleared, kill packet and advance buffer.
+ // Otherwise forward the pts value for the current packet.
+ if(Kit_GetRingBufferLength(packet->rb) == 0) {
+ Kit_AdvanceDecoderOutput(dec);
+ dec->clock_pos = packet->pts;
+ free_out_audio_packet_cb(packet);
+ } else {
+ packet->pts += ((double)ret) / bytes_per_second;
+ dec->clock_pos = packet->pts;
+ }
+
+ return ret;
+}
diff --git a/src/internal/kitdecoder.c b/src/internal/kitdecoder.c
new file mode 100644
index 0000000..0855eee
--- /dev/null
+++ b/src/internal/kitdecoder.c
@@ -0,0 +1,282 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/kiterror.h"
+
+#define BUFFER_IN_SIZE 256
+
+static void free_in_video_packet_cb(void *packet) {
+ av_packet_free((AVPacket**)&packet);
+}
+
+Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index,
+ int out_b_size, dec_free_packet_cb free_out_cb,
+ int thread_count) {
+ assert(src != NULL);
+ assert(out_b_size > 0);
+ assert(thread_count > 0);
+
+ AVCodecContext *codec_ctx = NULL;
+ AVCodec *codec = NULL;
+ AVFormatContext *format_ctx = src->format_ctx;
+ int bsizes[2] = {BUFFER_IN_SIZE, out_b_size};
+ dec_free_packet_cb free_hooks[2] = {free_in_video_packet_cb, free_out_cb};
+
+ // Make sure index seems correct
+ if(stream_index >= (int)format_ctx->nb_streams || stream_index < 0) {
+ Kit_SetError("Invalid stream %d", stream_index);
+ goto exit_0;
+ }
+
+ // Allocate decoder and make sure allocation was a success
+ Kit_Decoder *dec = calloc(1, sizeof(Kit_Decoder));
+ if(dec == NULL) {
+ Kit_SetError("Unable to allocate kit decoder for stream %d", stream_index);
+ goto exit_0;
+ }
+
+ // Find audio decoder
+ codec = avcodec_find_decoder(format_ctx->streams[stream_index]->codec->codec_id);
+ if(!codec) {
+ Kit_SetError("No suitable decoder found for stream %d", stream_index);
+ goto exit_1;
+ }
+
+ // Allocate a context for the codec
+ codec_ctx = avcodec_alloc_context3(codec);
+ if(codec_ctx == NULL) {
+ Kit_SetError("Unable to allocate codec context for stream %d", stream_index);
+ goto exit_1;
+ }
+
+ // Copy context from stream to target codec context
+ if(avcodec_copy_context(codec_ctx, format_ctx->streams[stream_index]->codec) != 0) {
+ Kit_SetError("Unable to copy codec context for stream %d", stream_index);
+ goto exit_2;
+ }
+
+ // Set thread count
+ codec_ctx->thread_count = thread_count;
+ codec_ctx->thread_type = FF_THREAD_SLICE;
+
+ // Open the stream
+ if(avcodec_open2(codec_ctx, codec, NULL) < 0) {
+ Kit_SetError("Unable to open codec for stream %d", stream_index);
+ goto exit_2;
+ }
+
+ // Set index and codec
+ dec->stream_index = stream_index;
+ dec->codec_ctx = codec_ctx;
+ dec->format_ctx = format_ctx;
+
+ // Allocate input/output ringbuffers
+ for(int i = 0; i < 2; i++) {
+ dec->buffer[i] = Kit_CreateBuffer(bsizes[i], free_hooks[i]);
+ if(dec->buffer[i] == NULL) {
+ Kit_SetError("Unable to allocate buffer for stream %d: %s", stream_index, SDL_GetError());
+ goto exit_3;
+ }
+ }
+
+ // Create a lock for output buffer synchronization
+ dec->output_lock = SDL_CreateMutex();
+ if(dec->output_lock == NULL) {
+ Kit_SetError("Unable to allocate mutex for stream %d: %s", stream_index, SDL_GetError());
+ goto exit_3;
+ }
+
+ // That's that
+ return dec;
+
+exit_3:
+ for(int i = 0; i < KIT_DEC_BUF_COUNT; i++) {
+ Kit_DestroyBuffer(dec->buffer[i]);
+ }
+ avcodec_close(codec_ctx);
+exit_2:
+ avcodec_free_context(&codec_ctx);
+exit_1:
+ free(dec);
+exit_0:
+ return NULL;
+}
+
+void Kit_CloseDecoder(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+ if(dec->dec_close) {
+ dec->dec_close(dec);
+ }
+ for(int i = 0; i < KIT_DEC_BUF_COUNT; i++) {
+ Kit_DestroyBuffer(dec->buffer[i]);
+ }
+ SDL_DestroyMutex(dec->output_lock);
+ avcodec_close(dec->codec_ctx);
+ avcodec_free_context(&dec->codec_ctx);
+ free(dec);
+}
+
+int Kit_RunDecoder(Kit_Decoder *dec) {
+ if(dec == NULL) return 0;
+
+ AVPacket *in_packet;
+ int is_output_full = 1;
+
+ // First, check if there is room in output buffer
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ is_output_full = Kit_IsBufferFull(dec->buffer[KIT_DEC_BUF_OUT]);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+ if(is_output_full) {
+ return 0;
+ }
+
+ // Then, see if we have incoming data
+ in_packet = Kit_ReadDecoderInput(dec);
+ if(in_packet == NULL) {
+ return 0;
+ }
+
+ // Run decoder with incoming packet
+ dec->dec_decode(dec, in_packet);
+
+ // Free raw packet before returning
+ av_packet_free(&in_packet);
+ return 1;
+}
+
+// ---- Information API ----
+
+int Kit_GetDecoderCodecInfo(const Kit_Decoder *dec, Kit_Codec *codec) {
+ if(dec == NULL) {
+ memset(codec, 0, sizeof(Kit_Codec));
+ return 1;
+ }
+ codec->threads = dec->codec_ctx->thread_count;
+ snprintf(codec->name, KIT_CODEC_NAME_MAX, "%s", dec->codec_ctx->codec->name);
+ snprintf(codec->description, KIT_CODEC_DESC_MAX, "%s", dec->codec_ctx->codec->long_name);
+ return 0;
+}
+
+int Kit_GetDecoderOutputFormat(const Kit_Decoder *dec, Kit_OutputFormat *output) {
+ if(dec == NULL) {
+ memset(output, 0, sizeof(Kit_OutputFormat));
+ return 1;
+ }
+ memcpy(output, &dec->output, sizeof(Kit_OutputFormat));
+ return 0;
+}
+
+int Kit_GetDecoderStreamIndex(const Kit_Decoder *dec) {
+ if(dec == NULL)
+ return -1;
+ return dec->stream_index;
+}
+
+// ---- Clock handling ----
+
+void Kit_SetDecoderClockSync(Kit_Decoder *dec, double sync) {
+ if(dec == NULL)
+ return;
+ dec->clock_sync = sync;
+}
+
+void Kit_ChangeDecoderClockSync(Kit_Decoder *dec, double sync) {
+ if(dec == NULL)
+ return;
+ dec->clock_sync += sync;
+}
+
+// ---- Input buffer handling ----
+
+int Kit_WriteDecoderInput(Kit_Decoder *dec, AVPacket *packet) {
+ assert(dec != NULL);
+ return Kit_WriteBuffer(dec->buffer[KIT_DEC_BUF_IN], packet);
+}
+
+bool Kit_CanWriteDecoderInput(Kit_Decoder *dec) {
+ assert(dec != NULL);
+ return !Kit_IsBufferFull(dec->buffer[KIT_DEC_BUF_IN]);
+}
+
+AVPacket* Kit_ReadDecoderInput(Kit_Decoder *dec) {
+ assert(dec != NULL);
+ return Kit_ReadBuffer(dec->buffer[KIT_DEC_BUF_IN]);
+}
+
+void Kit_ClearDecoderInput(Kit_Decoder *dec) {
+ Kit_ClearBuffer(dec->buffer[KIT_DEC_BUF_IN]);
+}
+
+// ---- Output buffer handling ----
+
+int Kit_WriteDecoderOutput(Kit_Decoder *dec, void *packet) {
+ assert(dec != NULL);
+ int ret = 1;
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ ret = Kit_WriteBuffer(dec->buffer[KIT_DEC_BUF_OUT], packet);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+ return ret;
+}
+
+void Kit_ClearDecoderOutput(Kit_Decoder *dec) {
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ Kit_ClearBuffer(dec->buffer[KIT_DEC_BUF_OUT]);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+}
+
+void* Kit_PeekDecoderOutput(Kit_Decoder *dec) {
+ assert(dec != NULL);
+ void *ret = NULL;
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ ret = Kit_PeekBuffer(dec->buffer[KIT_DEC_BUF_OUT]);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+ return ret;
+}
+
+void* Kit_ReadDecoderOutput(Kit_Decoder *dec) {
+ assert(dec != NULL);
+ void *ret = NULL;
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ ret = Kit_ReadBuffer(dec->buffer[KIT_DEC_BUF_OUT]);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+ return ret;
+}
+
+void Kit_ForEachDecoderOutput(Kit_Decoder *dec, Kit_ForEachItemCallback cb, void *userdata) {
+ assert(dec != NULL);
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ Kit_ForEachItemInBuffer(dec->buffer[KIT_DEC_BUF_OUT], cb, userdata);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+}
+
+void Kit_AdvanceDecoderOutput(Kit_Decoder *dec) {
+ assert(dec != NULL);
+ if(SDL_LockMutex(dec->output_lock) == 0) {
+ Kit_AdvanceBuffer(dec->buffer[KIT_DEC_BUF_OUT]);
+ SDL_UnlockMutex(dec->output_lock);
+ }
+}
+
+void Kit_ClearDecoderBuffers(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+ Kit_ClearDecoderInput(dec);
+ Kit_ClearDecoderOutput(dec);
+ avcodec_flush_buffers(dec->codec_ctx);
+}
+
+int Kit_LockDecoderOutput(Kit_Decoder *dec) {
+ return SDL_LockMutex(dec->output_lock);
+}
+
+void Kit_UnlockDecoderOutput(Kit_Decoder *dec) {
+ SDL_UnlockMutex(dec->output_lock);
+}
diff --git a/src/kitlibstate.c b/src/internal/kitlibstate.c
index 0dd4e6b..4482ee9 100644
--- a/src/kitlibstate.c
+++ b/src/internal/kitlibstate.c
@@ -1,6 +1,7 @@
+#include <stdlib.h>
#include "kitchensink/internal/kitlibstate.h"
-static Kit_LibraryState _librarystate = {0, NULL};
+static Kit_LibraryState _librarystate = {0, 1, 0, 3, 64, 64, NULL, NULL};
Kit_LibraryState* Kit_GetLibraryState() {
return &_librarystate;
diff --git a/src/internal/libass.c b/src/internal/libass.c
new file mode 100644
index 0000000..4a43e86
--- /dev/null
+++ b/src/internal/libass.c
@@ -0,0 +1,26 @@
+#ifdef USE_DYNAMIC_LIBASS
+
+#include <SDL_loadso.h>
+#include "kitchensink/internal/libass.h"
+
+int load_libass(void *handle) {
+ ass_library_init = SDL_LoadFunction(handle, "ass_library_init");
+ ass_library_done = SDL_LoadFunction(handle, "ass_library_done");
+ ass_set_message_cb = SDL_LoadFunction(handle, "ass_set_message_cb");
+ ass_renderer_init = SDL_LoadFunction(handle, "ass_renderer_init");
+ ass_renderer_done = SDL_LoadFunction(handle, "ass_renderer_done");
+ ass_set_frame_size = SDL_LoadFunction(handle, "ass_set_frame_size");
+ ass_set_hinting = SDL_LoadFunction(handle, "ass_set_hinting");
+ ass_set_fonts = SDL_LoadFunction(handle, "ass_set_fonts");
+ ass_render_frame = SDL_LoadFunction(handle, "ass_render_frame");
+ ass_new_track = SDL_LoadFunction(handle, "ass_new_track");
+ ass_free_track = SDL_LoadFunction(handle, "ass_free_track");
+ ass_process_data = SDL_LoadFunction(handle, "ass_process_data");
+ ass_add_font = SDL_LoadFunction(handle, "ass_add_font");
+ ass_process_codec_private = SDL_LoadFunction(handle, "ass_process_codec_private");
+ ass_process_chunk = SDL_LoadFunction(handle, "ass_process_chunk");
+ ass_set_storage_size = SDL_LoadFunction(handle, "ass_set_storage_size");
+ return 0;
+}
+
+#endif // USE_DYNAMIC_LIBASS
diff --git a/src/internal/subtitle/kitatlas.c b/src/internal/subtitle/kitatlas.c
new file mode 100644
index 0000000..e7bb654
--- /dev/null
+++ b/src/internal/subtitle/kitatlas.c
@@ -0,0 +1,187 @@
+#include <assert.h>
+
+#include "kitchensink/internal/subtitle/kitatlas.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+static int min(int a, int b) {
+ if(a < b)
+ return a;
+ return b;
+}
+
+
+Kit_TextureAtlas* Kit_CreateAtlas() {
+ Kit_TextureAtlas *atlas = calloc(1, sizeof(Kit_TextureAtlas));
+ if(atlas == NULL) {
+ goto exit_0;
+ }
+ atlas->cur_items = 0;
+ atlas->max_items = 1024;
+ atlas->max_shelves = 256;
+ atlas->w = 0;
+ atlas->h = 0;
+
+ // Allocate items. These hold the surfaces that should be in atlas
+ atlas->items = calloc(atlas->max_items, sizeof(Kit_TextureAtlasItem));
+ if(atlas->items == NULL) {
+ goto exit_1;
+ }
+
+ // Allocate shelves. These describe the used space of the atlas
+ atlas->shelves = calloc(atlas->max_shelves, sizeof(Kit_Shelf));
+ if(atlas->shelves == NULL) {
+ goto exit_2;
+ }
+
+ return atlas;
+
+exit_2:
+ free(atlas->items);
+exit_1:
+ free(atlas);
+exit_0:
+ return NULL;
+}
+
+void Kit_ClearAtlasContent(Kit_TextureAtlas *atlas) {
+ atlas->cur_items = 0;
+ memset(atlas->items, 0, atlas->max_items * sizeof(Kit_TextureAtlasItem));
+ memset(atlas->shelves, 0, atlas->max_shelves * sizeof(Kit_Shelf));
+}
+
+void Kit_FreeAtlas(Kit_TextureAtlas *atlas) {
+ assert(atlas != NULL);
+ free(atlas->items);
+ free(atlas->shelves);
+ free(atlas);
+}
+
+void Kit_SetItemAllocation(Kit_TextureAtlasItem *item, SDL_Surface *surface, int shelf, int slot, int x, int y) {
+ assert(item != NULL);
+
+ item->cur_shelf = shelf;
+ item->cur_slot = slot;
+ item->source.x = x;
+ item->source.y = y;
+ item->source.w = surface->w;
+ item->source.h = surface->h;
+}
+
+int Kit_FindFreeAtlasSlot(Kit_TextureAtlas *atlas, SDL_Surface *surface, Kit_TextureAtlasItem *item) {
+ assert(atlas != NULL);
+ assert(item != NULL);
+
+ int shelf_w;
+ int shelf_h;
+ int total_remaining_h = atlas->h;
+ int total_reserved_h = 0;
+
+ // First, try to look for a good, existing shelf
+ int best_shelf_idx = -1;
+ int best_shelf_h = atlas->h;
+ int best_shelf_y = 0;
+
+ // Try to find a good shelf to put this item in
+ int shelf_idx;
+ for(shelf_idx = 0; shelf_idx < atlas->max_shelves; shelf_idx++) {
+ shelf_w = atlas->shelves[shelf_idx].width;
+ shelf_h = atlas->shelves[shelf_idx].height;
+ if(shelf_h == 0) {
+ break;
+ }
+ total_remaining_h -= shelf_h;
+ total_reserved_h += shelf_h;
+
+ // If the item fits, check if the space is better than previous one
+ if(surface->w <= (atlas->w - shelf_w) && surface->h <= shelf_h && shelf_h < best_shelf_h) {
+ best_shelf_h = shelf_h;
+ best_shelf_idx = shelf_idx;
+ best_shelf_y = total_reserved_h - shelf_h;
+ }
+ }
+
+ // If existing shelf found, put the item there. Otherwise create a new shelf.
+ if(best_shelf_idx != -1) {
+ Kit_SetItemAllocation(
+ item,
+ surface,
+ best_shelf_idx,
+ atlas->shelves[best_shelf_idx].count,
+ atlas->shelves[best_shelf_idx].width,
+ best_shelf_y);
+ atlas->shelves[best_shelf_idx].width += surface->w;
+ atlas->shelves[best_shelf_idx].count += 1;
+ return 0;
+ } else if(total_remaining_h >= surface->h) {
+ atlas->shelves[shelf_idx].width = surface->w;
+ atlas->shelves[shelf_idx].height = surface->h;
+ atlas->shelves[shelf_idx].count = 1;
+ Kit_SetItemAllocation(
+ item,
+ surface,
+ shelf_idx,
+ 0,
+ 0,
+ total_reserved_h);
+ return 0;
+ }
+
+ return 1; // Can't fit!
+}
+
+void Kit_CheckAtlasTextureSize(Kit_TextureAtlas *atlas, SDL_Texture *texture) {
+ assert(atlas != NULL);
+ assert(texture != NULL);
+
+ // Check if texture size has changed, and clear content if it has.
+ int texture_w;
+ int texture_h;
+ if(SDL_QueryTexture(texture, NULL, NULL, &texture_w, &texture_h) == 0) {
+ atlas->w = texture_w;
+ atlas->h = texture_h;
+ }
+}
+
+int Kit_GetAtlasItems(const Kit_TextureAtlas *atlas, SDL_Rect *sources, SDL_Rect *targets, int limit) {
+ assert(atlas != NULL);
+ assert(limit >= 0);
+
+ int max_count = min(atlas->cur_items, limit);
+ for(int i = 0; i < max_count; i++) {
+ Kit_TextureAtlasItem *item = &atlas->items[i];
+ if(sources != NULL)
+ memcpy(&sources[i], &item->source, sizeof(SDL_Rect));
+ if(targets != NULL)
+ memcpy(&targets[i], &item->target, sizeof(SDL_Rect));
+ }
+ return max_count;
+}
+
+int Kit_AddAtlasItem(Kit_TextureAtlas *atlas, SDL_Texture *texture, SDL_Surface *surface, const SDL_Rect *target) {
+ assert(atlas != NULL);
+ assert(surface != NULL);
+ assert(target != NULL);
+
+ // Make sure there is still room
+ if(atlas->cur_items >= atlas->max_items)
+ return -1;
+
+ // Create a new item
+ Kit_TextureAtlasItem item;
+ memset(&item, 0, sizeof(Kit_TextureAtlasItem));
+ memcpy(&item.target, target, sizeof(SDL_Rect));
+ item.cur_shelf = -1;
+ item.cur_slot = -1;
+
+ // Allocate space for the new item
+ if(Kit_FindFreeAtlasSlot(atlas, surface, &item) != 0) {
+ return -1;
+ }
+
+ // And update texture with the surface
+ SDL_UpdateTexture(texture, &item.source, surface->pixels, surface->pitch);
+
+ // Room found, add item to the atlas
+ memcpy(&atlas->items[atlas->cur_items++], &item, sizeof(Kit_TextureAtlasItem));
+ return 0;
+}
diff --git a/src/internal/subtitle/kitsubtitle.c b/src/internal/subtitle/kitsubtitle.c
new file mode 100644
index 0000000..d6bed5d
--- /dev/null
+++ b/src/internal/subtitle/kitsubtitle.c
@@ -0,0 +1,190 @@
+#include <assert.h>
+
+#include <SDL.h>
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/utils/kitlog.h"
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/kitlib.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/kitsubtitle.h"
+#include "kitchensink/internal/subtitle/kitatlas.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubimage.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubass.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+
+
+typedef struct Kit_SubtitleDecoder {
+ Kit_SubtitleRenderer *renderer;
+ AVSubtitle scratch_frame;
+ Kit_TextureAtlas *atlas;
+} Kit_SubtitleDecoder;
+
+
+static void free_out_subtitle_packet_cb(void *packet) {
+ Kit_FreeSubtitlePacket((Kit_SubtitlePacket*)packet);
+}
+
+static void dec_decode_subtitle_cb(Kit_Decoder *dec, AVPacket *in_packet) {
+ assert(dec != NULL);
+ assert(in_packet != NULL);
+
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ double pts;
+ double start;
+ double end;
+ int frame_finished;
+ int len;
+
+ if(in_packet->size > 0) {
+ len = avcodec_decode_subtitle2(dec->codec_ctx, &subtitle_dec->scratch_frame, &frame_finished, in_packet);
+ if(len < 0) {
+ return;
+ }
+
+ if(frame_finished) {
+ // Start and end presentation timestamps for subtitle frame
+ pts = 0;
+ if(in_packet->pts != AV_NOPTS_VALUE) {
+ pts = in_packet->pts;
+ pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base);
+ }
+
+ // If subtitle has no ending time, we set some safety value.
+ if(subtitle_dec->scratch_frame.end_display_time == UINT_MAX) {
+ subtitle_dec->scratch_frame.end_display_time = 30000;
+ }
+
+ start = pts + subtitle_dec->scratch_frame.start_display_time / 1000.0F;
+ end = pts + subtitle_dec->scratch_frame.end_display_time / 1000.0F;
+
+ // Create a packet. This should be filled by renderer.
+ Kit_RunSubtitleRenderer(
+ subtitle_dec->renderer, &subtitle_dec->scratch_frame, start, end);
+
+ // Free subtitle since it has now been handled
+ avsubtitle_free(&subtitle_dec->scratch_frame);
+ }
+ }
+}
+
+static void dec_close_subtitle_cb(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ Kit_FreeAtlas(subtitle_dec->atlas);
+ Kit_CloseSubtitleRenderer(subtitle_dec->renderer);
+ free(subtitle_dec);
+}
+
+Kit_Decoder* Kit_CreateSubtitleDecoder(const Kit_Source *src, int stream_index, int video_w, int video_h, int screen_w, int screen_h) {
+ assert(src != NULL);
+ assert(video_w >= 0);
+ assert(video_h >= 0);
+ assert(screen_w >= 0);
+ assert(screen_h >= 0);
+
+ if(stream_index < 0) {
+ return NULL;
+ }
+
+ Kit_LibraryState *state = Kit_GetLibraryState();
+
+ // First the generic decoder component
+ Kit_Decoder *dec = Kit_CreateDecoder(
+ src,
+ stream_index,
+ state->subtitle_buf_frames,
+ free_out_subtitle_packet_cb,
+ state->thread_count);
+ if(dec == NULL) {
+ Kit_SetError("Unable to allocate subtitle decoder");
+ goto exit_0;
+ }
+
+ // ... then allocate the subtitle decoder
+ Kit_SubtitleDecoder *subtitle_dec = calloc(1, sizeof(Kit_SubtitleDecoder));
+ if(subtitle_dec == NULL) {
+ Kit_SetError("Unable to allocate subtitle decoder");
+ goto exit_1;
+ }
+
+ // Set format. Note that is_enabled may be changed below ...
+ Kit_OutputFormat output;
+ memset(&output, 0, sizeof(Kit_OutputFormat));
+ output.format = SDL_PIXELFORMAT_RGBA32; // Always this
+
+ // For subtitles, we need a renderer for the stream. Pick one based on codec ID.
+ switch(dec->codec_ctx->codec_id) {
+ case AV_CODEC_ID_TEXT:
+ case AV_CODEC_ID_HDMV_TEXT_SUBTITLE:
+ case AV_CODEC_ID_SRT:
+ case AV_CODEC_ID_SUBRIP:
+ case AV_CODEC_ID_SSA:
+ case AV_CODEC_ID_ASS:
+ if(state->init_flags & KIT_INIT_ASS) {
+ subtitle_dec->renderer = Kit_CreateASSSubtitleRenderer(dec, video_w, video_h, screen_w, screen_h);
+ }
+ break;
+ case AV_CODEC_ID_DVD_SUBTITLE:
+ case AV_CODEC_ID_DVB_SUBTITLE:
+ case AV_CODEC_ID_HDMV_PGS_SUBTITLE:
+ case AV_CODEC_ID_XSUB:
+ subtitle_dec->renderer = Kit_CreateImageSubtitleRenderer(dec, video_w, video_h, screen_w, screen_h);
+ break;
+ default:
+ Kit_SetError("Unrecognized subtitle format");
+ break;
+ }
+ if(subtitle_dec->renderer == NULL) {
+ goto exit_2;
+ }
+
+ // Allocate texture atlas for subtitle rectangles
+ subtitle_dec->atlas = Kit_CreateAtlas();
+ if(subtitle_dec->atlas == NULL) {
+ Kit_SetError("Unable to allocate subtitle texture atlas");
+ goto exit_3;
+ }
+
+ // Set callbacks and userdata, and we're go
+ dec->dec_decode = dec_decode_subtitle_cb;
+ dec->dec_close = dec_close_subtitle_cb;
+ dec->userdata = subtitle_dec;
+ dec->output = output;
+ return dec;
+
+exit_3:
+ Kit_CloseSubtitleRenderer(subtitle_dec->renderer);
+exit_2:
+ free(subtitle_dec);
+exit_1:
+ Kit_CloseDecoder(dec);
+exit_0:
+ return NULL;
+}
+
+void Kit_SetSubtitleDecoderSize(Kit_Decoder *dec, int screen_w, int screen_h) {
+ assert(dec != NULL);
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ Kit_SetSubtitleRendererSize(subtitle_dec->renderer, screen_w, screen_h);
+}
+
+void Kit_GetSubtitleDecoderTexture(Kit_Decoder *dec, SDL_Texture *texture) {
+ assert(dec != NULL);
+ assert(texture != NULL);
+
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ double sync_ts = _GetSystemTime() - dec->clock_sync;
+
+ // Tell the renderer to render content to atlas
+ Kit_GetSubtitleRendererData(subtitle_dec->renderer, subtitle_dec->atlas, texture, sync_ts);
+}
+
+int Kit_GetSubtitleDecoderInfo(Kit_Decoder *dec, SDL_Texture *texture, SDL_Rect *sources, SDL_Rect *targets, int limit) {
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ return Kit_GetAtlasItems(subtitle_dec->atlas, sources, targets, limit);
+}
diff --git a/src/internal/subtitle/kitsubtitlepacket.c b/src/internal/subtitle/kitsubtitlepacket.c
new file mode 100644
index 0000000..f44eee6
--- /dev/null
+++ b/src/internal/subtitle/kitsubtitlepacket.c
@@ -0,0 +1,23 @@
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+
+
+Kit_SubtitlePacket* Kit_CreateSubtitlePacket(
+ bool clear, double pts_start, double pts_end, int pos_x, int pos_y, SDL_Surface *surface)
+{
+ Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket));
+ p->pts_start = pts_start;
+ p->pts_end = pts_end;
+ p->x = pos_x;
+ p->y = pos_y;
+ p->surface = surface;
+ if(p->surface != NULL) {
+ p->surface->refcount++; // We dont want to needlessly copy; instead increase refcount.
+ }
+ p->clear = clear;
+ return p;
+}
+
+void Kit_FreeSubtitlePacket(Kit_SubtitlePacket *p) {
+ SDL_FreeSurface(p->surface);
+ free(p);
+}
diff --git a/src/internal/subtitle/renderers/kitsubass.c b/src/internal/subtitle/renderers/kitsubass.c
new file mode 100644
index 0000000..7db72dd
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubass.c
@@ -0,0 +1,212 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include <SDL_surface.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/kitatlas.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubass.h"
+
+typedef struct Kit_ASSSubtitleRenderer {
+ ASS_Renderer *renderer;
+ ASS_Track *track;
+} Kit_ASSSubtitleRenderer;
+
+static void Kit_ProcessAssImage(SDL_Surface *surface, const ASS_Image *img) {
+ unsigned char r = ((img->color) >> 24) & 0xFF;
+ unsigned char g = ((img->color) >> 16) & 0xFF;
+ unsigned char b = ((img->color) >> 8) & 0xFF;
+ unsigned char a = 0xFF - ((img->color) & 0xFF);
+ unsigned char *src = img->bitmap;
+ unsigned char *dst = surface->pixels;
+ unsigned int x;
+ unsigned int y;
+ unsigned int rx;
+
+ for(y = 0; y < img->h; y++) {
+ for(x = 0; x < img->w; x++) {
+ rx = x * 4;
+ dst[rx + 0] = r;
+ dst[rx + 1] = g;
+ dst[rx + 2] = b;
+ dst[rx + 3] = (a * src[x]) >> 8;
+ }
+ src += img->stride;
+ dst += surface->pitch;
+ }
+}
+
+static void ren_render_ass_cb(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts) {
+ assert(ren != NULL);
+ assert(src != NULL);
+
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ AVSubtitle *sub = src;
+
+ // Read incoming subtitle packets to libASS
+ if(Kit_LockDecoderOutput(ren->dec) == 0) {
+ for(int r = 0; r < sub->num_rects; r++) {
+ if(sub->rects[r]->ass == NULL)
+ continue;
+ ass_process_data(ass_ren->track, sub->rects[r]->ass, strlen(sub->rects[r]->ass));
+ }
+ Kit_UnlockDecoderOutput(ren->dec);
+ }
+}
+
+static void ren_close_ass_cb(Kit_SubtitleRenderer *ren) {
+ if(ren == NULL) return;
+
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ ass_free_track(ass_ren->track);
+ ass_renderer_done(ass_ren->renderer);
+ free(ass_ren);
+}
+
+static int ren_get_ass_data_cb(Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts) {
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ SDL_Surface *dst = NULL;
+ ASS_Image *src = NULL;
+ int change = 0;
+ unsigned int now = current_pts * 1000;
+
+ if(Kit_LockDecoderOutput(ren->dec) == 0) {
+ // Tell ASS to render some images
+ src = ass_render_frame(ass_ren->renderer, ass_ren->track, now, &change);
+
+ // If there was no change, stop here
+ if(change == 0) {
+ Kit_UnlockDecoderOutput(ren->dec);
+ return 0;
+ }
+
+ // There was some change, process images and add them to atlas
+ Kit_ClearAtlasContent(atlas);
+ Kit_CheckAtlasTextureSize(atlas, texture);
+ for(; src; src = src->next) {
+ if(src->w == 0 || src->h == 0)
+ continue;
+ dst = SDL_CreateRGBSurfaceWithFormat(0, src->w, src->h, 32, SDL_PIXELFORMAT_RGBA32);
+ Kit_ProcessAssImage(dst, src);
+ SDL_Rect target;
+ target.x = src->dst_x;
+ target.y = src->dst_y;
+ target.w = dst->w;
+ target.h = dst->h;
+ Kit_AddAtlasItem(atlas, texture, dst, &target);
+ SDL_FreeSurface(dst);
+ }
+
+ Kit_UnlockDecoderOutput(ren->dec);
+ }
+
+ ren->dec->clock_pos = current_pts;
+ return 0;
+}
+
+static void ren_set_ass_size_cb(Kit_SubtitleRenderer *ren, int w, int h) {
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ ass_set_frame_size(ass_ren->renderer, w, h);
+}
+
+Kit_SubtitleRenderer* Kit_CreateASSSubtitleRenderer(Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h) {
+ assert(dec != NULL);
+ assert(video_w >= 0);
+ assert(video_h >= 0);
+ assert(screen_w >= 0);
+ assert(screen_h >= 0);
+
+ // Make sure that libass library has been initialized + get handle
+ Kit_LibraryState *state = Kit_GetLibraryState();
+ if(state->libass_handle == NULL) {
+ Kit_SetError("Libass library has not been initialized");
+ return NULL;
+ }
+
+ // First allocate the generic decoder component
+ Kit_SubtitleRenderer *ren = Kit_CreateSubtitleRenderer(dec);
+ if(ren == NULL) {
+ goto exit_0;
+ }
+
+ // Next, allocate ASS subtitle renderer context.
+ Kit_ASSSubtitleRenderer *ass_ren = calloc(1, sizeof(Kit_ASSSubtitleRenderer));
+ if(ass_ren == NULL) {
+ Kit_SetError("Unable to allocate ass subtitle renderer");
+ goto exit_1;
+ }
+
+ // Initialize libass renderer
+ ASS_Renderer *ass_renderer = ass_renderer_init(state->libass_handle);
+ if(ass_renderer == NULL) {
+ Kit_SetError("Unable to initialize libass renderer");
+ goto exit_2;
+ }
+
+ // Read fonts from attachment streams and give them to libass
+ for(int j = 0; j < dec->format_ctx->nb_streams; j++) {
+ AVStream *st = dec->format_ctx->streams[j];
+ if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) {
+ const AVDictionaryEntry *tag = av_dict_get(
+ st->metadata,
+ "filename",
+ NULL,
+ AV_DICT_MATCH_CASE);
+ if(tag) {
+ ass_add_font(
+ state->libass_handle,
+ tag->value,
+ (char*)st->codec->extradata,
+ st->codec->extradata_size);
+ }
+ }
+ }
+
+ // Init libass fonts and window frame size
+ ass_set_fonts(
+ ass_renderer,
+ NULL, "sans-serif",
+ ASS_FONTPROVIDER_AUTODETECT,
+ NULL, 1);
+ ass_set_storage_size(ass_renderer, video_w, video_h);
+ ass_set_frame_size(ass_renderer, screen_w, screen_h);
+ ass_set_hinting(ass_renderer, state->font_hinting);
+
+ // Initialize libass track
+ ASS_Track *ass_track = ass_new_track(state->libass_handle);
+ if(ass_track == NULL) {
+ Kit_SetError("Unable to initialize libass track");
+ goto exit_3;
+ }
+
+ // Set up libass track headers (ffmpeg provides these)
+ if(dec->codec_ctx->subtitle_header) {
+ ass_process_codec_private(
+ ass_track,
+ (char*)dec->codec_ctx->subtitle_header,
+ dec->codec_ctx->subtitle_header_size);
+ }
+
+ // Set callbacks and userdata, and we're go
+ ass_ren->renderer = ass_renderer;
+ ass_ren->track = ass_track;
+ ren->ren_render = ren_render_ass_cb;
+ ren->ren_close = ren_close_ass_cb;
+ ren->ren_get_data = ren_get_ass_data_cb;
+ ren->ren_set_size = ren_set_ass_size_cb;
+ ren->userdata = ass_ren;
+ return ren;
+
+exit_3:
+ ass_renderer_done(ass_renderer);
+exit_2:
+ free(ass_ren);
+exit_1:
+ Kit_CloseSubtitleRenderer(ren);
+exit_0:
+ return NULL;
+}
diff --git a/src/internal/subtitle/renderers/kitsubimage.c b/src/internal/subtitle/renderers/kitsubimage.c
new file mode 100644
index 0000000..0cd0cce
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubimage.c
@@ -0,0 +1,144 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include <SDL_surface.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/subtitle/kitatlas.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubimage.h"
+
+
+typedef struct Kit_ImageSubtitleRenderer {
+ int video_w;
+ int video_h;
+ float scale_x;
+ float scale_y;
+} Kit_ImageSubtitleRenderer;
+
+static void ren_render_image_cb(Kit_SubtitleRenderer *ren, void *sub_src, double start_pts, double end_pts) {
+ assert(ren != NULL);
+ assert(sub_src != NULL);
+
+ AVSubtitle *sub = sub_src;
+ SDL_Surface *dst = NULL;
+ SDL_Surface *src = NULL;
+
+ // If this subtitle has no rects, we still need to clear screen from old subs
+ if(sub->num_rects == 0) {
+ Kit_WriteDecoderOutput(
+ ren->dec, Kit_CreateSubtitlePacket(true, start_pts, end_pts, 0, 0, NULL));
+ return;
+ }
+
+ // Convert subtitle images from paletted to RGBA8888
+ for(int n = 0; n < sub->num_rects; n++) {
+ AVSubtitleRect *r = sub->rects[n];
+ if(r->type != SUBTITLE_BITMAP)
+ continue;
+
+ src = SDL_CreateRGBSurfaceWithFormatFrom(
+ r->data[0], r->w, r->h, 8, r->linesize[0], SDL_PIXELFORMAT_INDEX8);
+ SDL_SetPaletteColors(src->format->palette, (SDL_Color*)r->data[1], 0, 256);
+ dst = SDL_CreateRGBSurfaceWithFormat(
+ 0, r->w, r->h, 32, SDL_PIXELFORMAT_RGBA32);
+
+ // Blit source to target and free source surface.
+ SDL_BlitSurface(src, NULL, dst, NULL);
+
+ // Create a new packet and write it to output buffer
+ Kit_WriteDecoderOutput(
+ ren->dec, Kit_CreateSubtitlePacket(false, start_pts, end_pts, r->x, r->y, dst));
+
+ // Free surfaces
+ SDL_FreeSurface(src);
+ SDL_FreeSurface(dst);
+ }
+}
+
+static int ren_get_img_data_cb(Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts) {
+ Kit_ImageSubtitleRenderer *img_ren = ren->userdata;
+ Kit_SubtitlePacket *packet = NULL;
+
+ Kit_CheckAtlasTextureSize(atlas, texture);
+ while((packet = Kit_PeekDecoderOutput(ren->dec)) != NULL) {
+ // Clear dead packets
+ if(packet->pts_end < current_pts) {
+ Kit_AdvanceDecoderOutput(ren->dec);
+ Kit_FreeSubtitlePacket(packet);
+ continue;
+ }
+
+ // Show visible ones
+ if(packet->pts_start < current_pts) {
+ if(packet->clear) {
+ Kit_ClearAtlasContent(atlas);
+ }
+ if(packet->surface != NULL) {
+ SDL_Rect target;
+ target.x = packet->x * img_ren->scale_x;
+ target.y = packet->y * img_ren->scale_y;
+ target.w = packet->surface->w * img_ren->scale_x;
+ target.h = packet->surface->h * img_ren->scale_y;
+ Kit_AddAtlasItem(atlas, texture, packet->surface, &target);
+ }
+ Kit_AdvanceDecoderOutput(ren->dec);
+ Kit_FreeSubtitlePacket(packet);
+ ren->dec->clock_pos = current_pts;
+ continue;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static void ren_set_img_size_cb(Kit_SubtitleRenderer *ren, int w, int h) {
+ Kit_ImageSubtitleRenderer *img_ren = ren->userdata;
+ img_ren->scale_x = (float)w / (float)img_ren->video_w;
+ img_ren->scale_y = (float)h / (float)img_ren->video_h;
+}
+
+static void ren_close_ass_cb(Kit_SubtitleRenderer *ren) {
+ if(ren == NULL) return;
+ free(ren->userdata);
+}
+
+Kit_SubtitleRenderer* Kit_CreateImageSubtitleRenderer(Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h) {
+ assert(dec != NULL);
+ assert(video_w >= 0);
+ assert(video_h >= 0);
+ assert(screen_w >= 0);
+ assert(screen_h >= 0);
+
+ // Allocate a new renderer
+ Kit_SubtitleRenderer *ren = Kit_CreateSubtitleRenderer(dec);
+ if(ren == NULL) {
+ goto exit_0;
+ }
+
+ // Allocate image renderer internal context
+ Kit_ImageSubtitleRenderer *img_ren = calloc(1, sizeof(Kit_ImageSubtitleRenderer));
+ if(img_ren == NULL) {
+ Kit_SetError("Unable to allocate image subtitle renderer");
+ goto exit_1;
+ }
+
+ // Only renderer required, no other data.
+ img_ren->video_w = video_w;
+ img_ren->video_h = video_h;
+ img_ren->scale_x = (float)screen_w / (float)video_w;
+ img_ren->scale_y = (float)screen_h / (float)video_h;
+ ren->ren_render = ren_render_image_cb;
+ ren->ren_get_data = ren_get_img_data_cb;
+ ren->ren_set_size = ren_set_img_size_cb;
+ ren->ren_close = ren_close_ass_cb;
+ ren->userdata = img_ren;
+ return ren;
+
+exit_1:
+ Kit_CloseSubtitleRenderer(ren);
+exit_0:
+ return NULL;
+}
diff --git a/src/internal/subtitle/renderers/kitsubrenderer.c b/src/internal/subtitle/renderers/kitsubrenderer.c
new file mode 100644
index 0000000..77ad201
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubrenderer.c
@@ -0,0 +1,48 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+
+
+Kit_SubtitleRenderer* Kit_CreateSubtitleRenderer(Kit_Decoder *dec) {
+ // Allocate renderer and make sure allocation was a success
+ Kit_SubtitleRenderer *ren = calloc(1, sizeof(Kit_SubtitleRenderer));
+ if(ren == NULL) {
+ Kit_SetError("Unable to allocate kit subtitle renderer");
+ return NULL;
+ }
+ ren->dec = dec;
+ return ren;
+}
+
+void Kit_RunSubtitleRenderer(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts) {
+ if(ren == NULL)
+ return;
+ if(ren->ren_render != NULL)
+ ren->ren_render(ren, src, start_pts, end_pts);
+}
+
+int Kit_GetSubtitleRendererData(Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts) {
+ if(ren == NULL)
+ return 0;
+ if(ren->ren_get_data != NULL)
+ return ren->ren_get_data(ren, atlas, texture, current_pts);
+ return 0;
+}
+
+void Kit_SetSubtitleRendererSize(Kit_SubtitleRenderer *ren, int w, int h) {
+ if(ren == NULL)
+ return;
+ if(ren->ren_set_size != NULL)
+ ren->ren_set_size(ren, w, h);
+}
+
+void Kit_CloseSubtitleRenderer(Kit_SubtitleRenderer *ren) {
+ if(ren == NULL)
+ return;
+ if(ren->ren_close != NULL)
+ ren->ren_close(ren);
+ free(ren);
+}
diff --git a/src/kitbuffer.c b/src/internal/utils/kitbuffer.c
index 5c492ea..0133154 100644
--- a/src/kitbuffer.c
+++ b/src/internal/utils/kitbuffer.c
@@ -1,8 +1,8 @@
-#include "kitchensink/internal/kitbuffer.h"
-
#include <stdlib.h>
#include <assert.h>
+#include "kitchensink/internal/utils/kitbuffer.h"
+
Kit_Buffer* Kit_CreateBuffer(unsigned int size, Kit_BufferFreeCallback free_cb) {
Kit_Buffer *b = calloc(1, sizeof(Kit_Buffer));
if(b == NULL) {
@@ -27,6 +27,8 @@ void Kit_DestroyBuffer(Kit_Buffer *buffer) {
void Kit_ClearBuffer(Kit_Buffer *buffer) {
void *data;
+ if(buffer->free_cb == NULL)
+ return;
while((data = Kit_ReadBuffer(buffer)) != NULL) {
buffer->free_cb(data);
}
@@ -47,12 +49,15 @@ void* Kit_ReadBuffer(Kit_Buffer *buffer) {
return NULL;
}
-KIT_LOCAL void* Kit_PeekBuffer(const Kit_Buffer *buffer) {
+void* Kit_PeekBuffer(const Kit_Buffer *buffer) {
assert(buffer != NULL);
- return buffer->data[buffer->read_p % buffer->size];
+ if(buffer->read_p < buffer->write_p) {
+ return buffer->data[buffer->read_p % buffer->size];
+ }
+ return NULL;
}
-KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
+void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
assert(buffer != NULL);
if(buffer->read_p < buffer->write_p) {
buffer->data[buffer->read_p % buffer->size] = NULL;
@@ -64,6 +69,18 @@ KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
}
}
+void Kit_ForEachItemInBuffer(const Kit_Buffer *buffer, Kit_ForEachItemCallback cb, void *userdata) {
+ unsigned int read_p = buffer->read_p;
+ unsigned int write_p = buffer->write_p;
+ while(read_p < write_p) {
+ cb(buffer->data[read_p++ % buffer->size], userdata);
+ if(read_p >= buffer->size) {
+ read_p = read_p % buffer->size;
+ write_p = write_p % buffer->size;
+ }
+ }
+}
+
int Kit_WriteBuffer(Kit_Buffer *buffer, void *ptr) {
assert(buffer != NULL);
assert(ptr != NULL);
diff --git a/src/internal/utils/kithelpers.c b/src/internal/utils/kithelpers.c
new file mode 100644
index 0000000..c68f1c7
--- /dev/null
+++ b/src/internal/utils/kithelpers.c
@@ -0,0 +1,30 @@
+#include <libavutil/time.h>
+#include <libavutil/avstring.h>
+
+#include "kitchensink/internal/utils/kithelpers.h"
+
+static const char * const font_mime[] = {
+ "application/x-font-ttf",
+ "application/x-font-truetype",
+ "application/x-truetype-font",
+ "application/x-font-opentype",
+ "application/vnd.ms-opentype",
+ "application/font-sfnt",
+ NULL
+};
+
+double _GetSystemTime() {
+ return (double)av_gettime() / 1000000.0;
+}
+
+bool attachment_is_font(AVStream *stream) {
+ AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE);
+ if(tag) {
+ for(int n = 0; font_mime[n]; n++) {
+ if(av_strcasecmp(font_mime[n], tag->value) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/src/kitringbuffer.c b/src/internal/utils/kitringbuffer.c
index 1711d52..70fd24f 100644
--- a/src/kitringbuffer.c
+++ b/src/internal/utils/kitringbuffer.c
@@ -1,17 +1,17 @@
/*
* Ringbuffer
*
- * Copyright (c) 2016, Tuomas Virtanen
+ * Copyright (c) 2017, Tuomas Virtanen
* license: MIT; see LICENSE for details.
*/
-#include "kitchensink/internal/kitringbuffer.h"
-
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
+#include "kitchensink/internal/utils/kitringbuffer.h"
+
/**
* Creates a new ringbuffer with the given size.
* @param size Size for the new ringbuffer
diff --git a/src/internal/video/kitvideo.c b/src/internal/video/kitvideo.c
new file mode 100644
index 0000000..1629b05
--- /dev/null
+++ b/src/internal/video/kitvideo.c
@@ -0,0 +1,301 @@
+#include <assert.h>
+
+#include <libavformat/avformat.h>
+#include <libavutil/imgutils.h>
+#include <libswscale/swscale.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitbuffer.h"
+#include "kitchensink/internal/video/kitvideo.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+#define KIT_VIDEO_SYNC_THRESHOLD 0.01
+
+enum AVPixelFormat supported_list[] = {
+ AV_PIX_FMT_YUV420P,
+ AV_PIX_FMT_YUYV422,
+ AV_PIX_FMT_UYVY422,
+ AV_PIX_FMT_NV12,
+ AV_PIX_FMT_NV21,
+ AV_PIX_FMT_RGB24,
+ AV_PIX_FMT_BGR24,
+ AV_PIX_FMT_RGB555,
+ AV_PIX_FMT_BGR555,
+ AV_PIX_FMT_RGB565,
+ AV_PIX_FMT_BGR565,
+ AV_PIX_FMT_BGRA,
+ AV_PIX_FMT_RGBA,
+ AV_PIX_FMT_NONE
+};
+
+typedef struct Kit_VideoDecoder {
+ struct SwsContext *sws;
+ AVFrame *scratch_frame;
+} Kit_VideoDecoder;
+
+typedef struct Kit_VideoPacket {
+ double pts;
+ AVFrame *frame;
+} Kit_VideoPacket;
+
+
+static Kit_VideoPacket* _CreateVideoPacket(AVFrame *frame, double pts) {
+ Kit_VideoPacket *p = calloc(1, sizeof(Kit_VideoPacket));
+ p->frame = frame;
+ p->pts = pts;
+ return p;
+}
+
+static unsigned int _FindSDLPixelFormat(enum AVPixelFormat fmt) {
+ switch(fmt) {
+ case AV_PIX_FMT_YUV420P:
+ return SDL_PIXELFORMAT_YV12;
+ case AV_PIX_FMT_YUYV422:
+ return SDL_PIXELFORMAT_YUY2;
+ case AV_PIX_FMT_UYVY422:
+ return SDL_PIXELFORMAT_UYVY;
+ case AV_PIX_FMT_NV12:
+ return SDL_PIXELFORMAT_NV12;
+ case AV_PIX_FMT_NV21:
+ return SDL_PIXELFORMAT_NV21;
+ default:
+ return SDL_PIXELFORMAT_RGBA32;
+ }
+}
+
+static enum AVPixelFormat _FindAVPixelFormat(unsigned int fmt) {
+ switch(fmt) {
+ case SDL_PIXELFORMAT_YV12: return AV_PIX_FMT_YUV420P;
+ case SDL_PIXELFORMAT_YUY2: return AV_PIX_FMT_YUYV422;
+ case SDL_PIXELFORMAT_UYVY: return AV_PIX_FMT_UYVY422;
+ case SDL_PIXELFORMAT_NV12: return AV_PIX_FMT_NV12;
+ case SDL_PIXELFORMAT_NV21: return AV_PIX_FMT_NV21;
+ case SDL_PIXELFORMAT_ARGB32: return AV_PIX_FMT_BGRA;
+ case SDL_PIXELFORMAT_RGBA32: return AV_PIX_FMT_RGBA;
+ case SDL_PIXELFORMAT_BGR24: return AV_PIX_FMT_BGR24;
+ case SDL_PIXELFORMAT_RGB24: return AV_PIX_FMT_RGB24;
+ case SDL_PIXELFORMAT_RGB555: return AV_PIX_FMT_RGB555;
+ case SDL_PIXELFORMAT_BGR555: return AV_PIX_FMT_BGR555;
+ case SDL_PIXELFORMAT_RGB565: return AV_PIX_FMT_RGB565;
+ case SDL_PIXELFORMAT_BGR565: return AV_PIX_FMT_BGR565;
+ default:
+ return AV_PIX_FMT_NONE;
+ }
+}
+
+static void free_out_video_packet_cb(void *packet) {
+ Kit_VideoPacket *p = packet;
+ av_freep(&p->frame->data[0]);
+ av_frame_free(&p->frame);
+ free(p);
+}
+
+static void dec_decode_video_cb(Kit_Decoder *dec, AVPacket *in_packet) {
+ assert(dec != NULL);
+ assert(in_packet != NULL);
+
+ Kit_VideoDecoder *video_dec = dec->userdata;
+ AVFrame *out_frame;
+ int frame_finished;
+ int len;
+ double pts;
+ Kit_VideoPacket *out_packet;
+
+ while(in_packet->size > 0) {
+ len = avcodec_decode_video2(dec->codec_ctx, video_dec->scratch_frame, &frame_finished, in_packet);
+ if(len < 0) {
+ return;
+ }
+
+ if(frame_finished) {
+ // Target frame
+ out_frame = av_frame_alloc();
+ av_image_alloc(
+ out_frame->data,
+ out_frame->linesize,
+ dec->codec_ctx->width,
+ dec->codec_ctx->height,
+ _FindAVPixelFormat(dec->output.format),
+ 1);
+
+ // Scale from source format to target format, don't touch the size
+ sws_scale(
+ video_dec->sws,
+ (const unsigned char * const *)video_dec->scratch_frame->data,
+ video_dec->scratch_frame->linesize,
+ 0,
+ dec->codec_ctx->height,
+ out_frame->data,
+ out_frame->linesize);
+
+ // Get presentation timestamp
+#ifndef FF_API_FRAME_GET_SET
+ pts = av_frame_get_best_effort_timestamp(video_dec->scratch_frame);
+#else
+ pts = video_dec->scratch_frame->best_effort_timestamp;
+#endif
+ pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base);
+
+ // Lock, write to audio buffer, unlock
+ out_packet = _CreateVideoPacket(out_frame, pts);
+ Kit_WriteDecoderOutput(dec, out_packet);
+ }
+ in_packet->size -= len;
+ in_packet->data += len;
+ }
+}
+
+static void dec_close_video_cb(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+
+ Kit_VideoDecoder *video_dec = dec->userdata;
+ if(video_dec->scratch_frame != NULL) {
+ av_frame_free(&video_dec->scratch_frame);
+ }
+ if(video_dec->sws != NULL) {
+ sws_freeContext(video_dec->sws);
+ }
+ free(video_dec);
+}
+
+Kit_Decoder* Kit_CreateVideoDecoder(const Kit_Source *src, int stream_index) {
+ assert(src != NULL);
+ if(stream_index < 0) {
+ return NULL;
+ }
+
+ Kit_LibraryState *state = Kit_GetLibraryState();
+
+ // First the generic decoder component ...
+ Kit_Decoder *dec = Kit_CreateDecoder(
+ src,
+ stream_index,
+ state->video_buf_frames,
+ free_out_video_packet_cb,
+ state->thread_count);
+ if(dec == NULL) {
+ goto exit_0;
+ }
+
+ // ... then allocate the video decoder
+ Kit_VideoDecoder *video_dec = calloc(1, sizeof(Kit_VideoDecoder));
+ if(video_dec == NULL) {
+ goto exit_1;
+ }
+
+ // Create temporary video frame
+ video_dec->scratch_frame = av_frame_alloc();
+ if(video_dec->scratch_frame == NULL) {
+ Kit_SetError("Unable to initialize temporary video frame");
+ goto exit_2;
+ }
+
+ // Find best output format for us
+ enum AVPixelFormat output_format = avcodec_find_best_pix_fmt_of_list(
+ supported_list, dec->codec_ctx->pix_fmt, 1, NULL);
+
+ // Set format configs
+ Kit_OutputFormat output;
+ memset(&output, 0, sizeof(Kit_OutputFormat));
+ output.width = dec->codec_ctx->width;
+ output.height = dec->codec_ctx->height;
+ output.format = _FindSDLPixelFormat(output_format);
+
+ // Create scaler for handling format changes
+ video_dec->sws = sws_getContext(
+ dec->codec_ctx->width, // Source w
+ dec->codec_ctx->height, // Source h
+ dec->codec_ctx->pix_fmt, // Source fmt
+ dec->codec_ctx->width, // Target w
+ dec->codec_ctx->height, // Target h
+ _FindAVPixelFormat(output.format), // Target fmt
+ SWS_BILINEAR,
+ NULL, NULL, NULL);
+ if(video_dec->sws == NULL) {
+ Kit_SetError("Unable to initialize video converter context");
+ goto exit_3;
+ }
+
+ // Set callbacks and userdata, and we're go
+ dec->dec_decode = dec_decode_video_cb;
+ dec->dec_close = dec_close_video_cb;
+ dec->userdata = video_dec;
+ dec->output = output;
+ return dec;
+
+exit_3:
+ av_frame_free(&video_dec->scratch_frame);
+exit_2:
+ free(video_dec);
+exit_1:
+ Kit_CloseDecoder(dec);
+exit_0:
+ return NULL;
+}
+
+int Kit_GetVideoDecoderData(Kit_Decoder *dec, SDL_Texture *texture) {
+ assert(dec != NULL);
+ assert(texture != NULL);
+
+ Kit_VideoPacket *packet = Kit_PeekDecoderOutput(dec);
+ if(packet == NULL) {
+ return 0;
+ }
+
+ double sync_ts = _GetSystemTime() - dec->clock_sync;
+
+ // Check if we want the packet
+ if(packet->pts > sync_ts + KIT_VIDEO_SYNC_THRESHOLD) {
+ // Video is ahead, don't show yet.
+ return 0;
+ } else if(packet->pts < sync_ts - KIT_VIDEO_SYNC_THRESHOLD) {
+ // Video is lagging, skip until we find a good PTS to continue from.
+ while(packet != NULL) {
+ Kit_AdvanceDecoderOutput(dec);
+ free_out_video_packet_cb(packet);
+ packet = Kit_PeekDecoderOutput(dec);
+ if(packet == NULL) {
+ break;
+ } else {
+ dec->clock_pos = packet->pts;
+ }
+ if(packet->pts > sync_ts - KIT_VIDEO_SYNC_THRESHOLD) {
+ break;
+ }
+ }
+ }
+
+ // If we have no viable packet, just skip
+ if(packet == NULL) {
+ return 0;
+ }
+
+ // Update output texture with current video data.
+ // Take formats into account.
+ switch(dec->output.format) {
+ case SDL_PIXELFORMAT_YV12:
+ case SDL_PIXELFORMAT_IYUV:
+ SDL_UpdateYUVTexture(
+ texture, NULL,
+ packet->frame->data[0], packet->frame->linesize[0],
+ packet->frame->data[1], packet->frame->linesize[1],
+ packet->frame->data[2], packet->frame->linesize[2]);
+ break;
+ default:
+ SDL_UpdateTexture(
+ texture, NULL,
+ packet->frame->data[0],
+ packet->frame->linesize[0]);
+ break;
+ }
+
+ // Advance buffer, and free the decoded frame.
+ Kit_AdvanceDecoderOutput(dec);
+ dec->clock_pos = packet->pts;
+ free_out_video_packet_cb(packet);
+
+ return 0;
+}
diff --git a/src/kiterror.c b/src/kiterror.c
index 2c87414..9ed0081 100644
--- a/src/kiterror.c
+++ b/src/kiterror.c
@@ -1,11 +1,11 @@
-#include "kitchensink/kitchensink.h"
-
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
+#include "kitchensink/kitchensink.h"
+
#define KIT_ERRBUFSIZE 1024
static char _error_available = false;
diff --git a/src/kitlib.c b/src/kitlib.c
index e5f1277..9cfd13e 100644
--- a/src/kitlib.c
+++ b/src/kitlib.c
@@ -1,35 +1,75 @@
+#include <assert.h>
+#include <SDL_loadso.h>
+
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/utils/kitlog.h"
#include "kitchensink/kitchensink.h"
#include "kitchensink/internal/kitlibstate.h"
-#include <libavformat/avformat.h>
-#include <ass/ass.h>
-#include <assert.h>
-// No-op
-void _libass_msg_callback(int level, const char *fmt, va_list va, void *data) {}
+static void _libass_msg_callback(int level, const char *fmt, va_list va, void *data) {}
+
+static int max(int a, int b) { return a > b ? a : b; }
+static int min(int a, int b) { return a < b ? a : b; }
+
+int Kit_InitASS(Kit_LibraryState *state) {
+#ifdef USE_DYNAMIC_LIBASS
+ state->ass_so_handle = SDL_LoadObject(DYNAMIC_LIBASS_NAME);
+ if(state->ass_so_handle == NULL) {
+ Kit_SetError("Unable to load ASS library");
+ return 1;
+ }
+ load_libass(state->ass_so_handle);
+#endif
+ state->libass_handle = ass_library_init();
+ state->thread_count = 1;
+ state->font_hinting = KIT_FONT_HINTING_NONE;
+ state->video_buf_frames = 3;
+ state->audio_buf_frames = 64;
+ state->subtitle_buf_frames = 64;
+ ass_set_message_cb(state->libass_handle, _libass_msg_callback, NULL);
+ return 0;
+}
+
+void Kit_CloseASS(Kit_LibraryState *state) {
+ ass_library_done(state->libass_handle);
+ state->libass_handle = NULL;
+#ifdef USE_DYNAMIC_LIBASS
+ SDL_UnloadObject(state->ass_so_handle);
+ state->ass_so_handle = NULL;
+#endif
+}
int Kit_Init(unsigned int flags) {
Kit_LibraryState *state = Kit_GetLibraryState();
if(state->init_flags != 0) {
- Kit_SetError("Kitchensink is already initialized.");
- return 1;
+ Kit_SetError("SDL_kitchensink is already initialized");
+ goto exit_0;
}
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
+ av_register_all();
+#endif
+
if(flags & KIT_INIT_NETWORK) {
avformat_network_init();
}
- if(flags & KIT_INIT_FORMATS) {
- av_register_all();
+ if(flags & KIT_INIT_ASS) {
+ if(Kit_InitASS(state) != 0) {
+ Kit_SetError("Failed to initialize libass");
+ goto exit_1;
+ }
}
state->init_flags = flags;
+ return 0;
- // Init libass
- state->libass_handle = ass_library_init();
+exit_1:
+ avformat_network_deinit();
- // Make libass message spam go away
- ass_set_message_cb(state->libass_handle, _libass_msg_callback, NULL);
-
- return 0;
+exit_0:
+ return 1;
}
void Kit_Quit() {
@@ -38,9 +78,49 @@ void Kit_Quit() {
if(state->init_flags & KIT_INIT_NETWORK) {
avformat_network_deinit();
}
+ if(state->init_flags & KIT_INIT_ASS) {
+ Kit_CloseASS(state);
+ }
state->init_flags = 0;
+}
- ass_library_done(state->libass_handle);
+void Kit_SetHint(Kit_HintType type, int value) {
+ Kit_LibraryState *state = Kit_GetLibraryState();
+ switch(type) {
+ case KIT_HINT_THREAD_COUNT:
+ state->thread_count = max(value, 1);
+ break;
+ case KIT_HINT_FONT_HINTING:
+ state->font_hinting = max(min(value, KIT_FONT_HINTING_COUNT), 0);
+ break;
+ case KIT_HINT_VIDEO_BUFFER_FRAMES:
+ state->video_buf_frames = min(value, 1);
+ break;
+ case KIT_HINT_AUDIO_BUFFER_FRAMES:
+ state->audio_buf_frames = min(value, 1);
+ break;
+ case KIT_HINT_SUBTITLE_BUFFER_FRAMES:
+ state->subtitle_buf_frames = min(value, 1);
+ break;
+ }
+}
+
+int Kit_GetHint(Kit_HintType type) {
+ Kit_LibraryState *state = Kit_GetLibraryState();
+ switch(type) {
+ case KIT_HINT_THREAD_COUNT:
+ return state->thread_count;
+ case KIT_HINT_FONT_HINTING:
+ return state->font_hinting;
+ case KIT_HINT_VIDEO_BUFFER_FRAMES:
+ return state->video_buf_frames;
+ case KIT_HINT_AUDIO_BUFFER_FRAMES:
+ return state->audio_buf_frames;
+ case KIT_HINT_SUBTITLE_BUFFER_FRAMES:
+ return state->subtitle_buf_frames;
+ default:
+ return 0;
+ }
}
void Kit_GetVersion(Kit_Version *version) {
diff --git a/src/kitlist.c b/src/kitlist.c
deleted file mode 100644
index b6d861c..0000000
--- a/src/kitlist.c
+++ /dev/null
@@ -1,79 +0,0 @@
-#include "kitchensink/internal/kitlist.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <assert.h>
-
-Kit_List* Kit_CreateList(unsigned int size, Kit_ListFreeCallback free_cb) {
- Kit_List *m = calloc(1, sizeof(Kit_List));
- if(m == NULL) {
- return NULL;
- }
- m->size = size;
- m->free_cb = free_cb;
- m->data = calloc(size, sizeof(void*));
- if(m->data == NULL) {
- free(m);
- return NULL;
- }
- return m;
-}
-
-void Kit_DestroyList(Kit_List *list) {
- if(list == NULL) return;
- Kit_ClearList(list);
- free(list->data);
- free(list);
-}
-
-void Kit_ClearList(Kit_List *list) {
- assert(list != NULL);
- for(unsigned int i = 0; i < list->size; i++) {
- if(list->data[i] != NULL) {
- list->free_cb(list->data[i]);
- list->data[i] = NULL;
- }
- }
- list->length = 0;
-}
-
-void Kit_RemoveFromList(Kit_List *list, unsigned int iterator) {
- assert(list != NULL);
- list->free_cb(list->data[iterator-1]);
- list->data[iterator-1] = NULL;
- list->length--;
-}
-
-void* Kit_IterateList(const Kit_List *list, unsigned int *iterator) {
- assert(list != NULL);
- assert(iterator != NULL);
- while((*iterator) < list->size) {
- void *ptr = list->data[(*iterator)];
- *iterator += 1;
- if(ptr != NULL) {
- return ptr;
- }
- }
- return NULL;
-}
-
-int Kit_WriteList(Kit_List *list, void *ptr) {
- assert(list != NULL);
- assert(ptr != NULL);
- if(list->length >= list->size) {
- return 1;
- }
- for(unsigned int i = 0; i < list->size; i++) {
- if(list->data[i] == NULL) {
- list->data[i] = ptr;
- list->length++;
- return 0;
- }
- }
- return 1;
-}
-
-int Kit_GetListLength(const Kit_List *list) {
- assert(list != NULL);
- return list->length;
-}
diff --git a/src/kitplayer.c b/src/kitplayer.c
index 9f8a963..5220945 100644
--- a/src/kitplayer.c
+++ b/src/kitplayer.c
@@ -1,829 +1,104 @@
-#include "kitchensink/kitplayer.h"
-#include "kitchensink/kiterror.h"
-#include "kitchensink/internal/kitbuffer.h"
-#include "kitchensink/internal/kitringbuffer.h"
-#include "kitchensink/internal/kitlist.h"
-#include "kitchensink/internal/kitlibstate.h"
-
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libswscale/swscale.h>
-#include <libswresample/swresample.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/time.h>
-#include <libavutil/samplefmt.h>
-#include <libavutil/avstring.h>
-#include <libavutil/imgutils.h>
-
-#include <SDL2/SDL.h>
-#include <ass/ass.h>
-
-#include <stdlib.h>
-#include <string.h>
#include <assert.h>
-#include <math.h>
-#define __STDC_FORMAT_MACROS
-#include <inttypes.h>
-
-// For compatibility
-#ifndef ASS_FONTPROVIDER_AUTODETECT
-#define ASS_FONTPROVIDER_AUTODETECT 1
-#endif
-
-// Threshold is in seconds
-#define VIDEO_SYNC_THRESHOLD 0.01
-#define AUDIO_SYNC_THRESHOLD 0.05
-
-// Buffersizes
-#define KIT_VBUFFERSIZE 3
-#define KIT_ABUFFERSIZE 64
-#define KIT_CBUFFERSIZE 8
-#define KIT_SBUFFERSIZE 512
-
-typedef enum Kit_ControlPacketType {
- KIT_CONTROL_SEEK,
- KIT_CONTROL_FLUSH
-} Kit_ControlPacketType;
-
-typedef struct Kit_VideoPacket {
- double pts;
- AVFrame *frame;
-} Kit_VideoPacket;
-
-typedef struct Kit_AudioPacket {
- double pts;
- size_t original_size;
- Kit_RingBuffer *rb;
-} Kit_AudioPacket;
-
-typedef struct Kit_ControlPacket {
- Kit_ControlPacketType type;
- double value1;
-} Kit_ControlPacket;
-
-typedef struct Kit_SubtitlePacket {
- double pts_start;
- double pts_end;
- SDL_Rect *rect;
- SDL_Surface *surface;
- SDL_Texture *texture;
-} Kit_SubtitlePacket;
-
-static int _InitCodecs(Kit_Player *player, const Kit_Source *src) {
- assert(player != NULL);
- assert(src != NULL);
- AVCodecContext *acodec_ctx = NULL;
- AVCodecContext *vcodec_ctx = NULL;
- AVCodecContext *scodec_ctx = NULL;
- AVCodec *acodec = NULL;
- AVCodec *vcodec = NULL;
- AVCodec *scodec = NULL;
- AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx;
-
- // Make sure index seems correct
- if(src->astream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid audio stream index: %d", src->astream_idx);
- goto exit_0;
- } else if(src->astream_idx >= 0) {
- // Find audio decoder
- acodec = avcodec_find_decoder(format_ctx->streams[src->astream_idx]->codec->codec_id);
- if(!acodec) {
- Kit_SetError("No suitable audio decoder found");
- goto exit_0;
- }
-
- // Copy the original audio codec context
- acodec_ctx = avcodec_alloc_context3(acodec);
- if(avcodec_copy_context(acodec_ctx, format_ctx->streams[src->astream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy audio codec context");
- goto exit_0;
- }
-
- // Create an audio decoder context
- if(avcodec_open2(acodec_ctx, acodec, NULL) < 0) {
- Kit_SetError("Unable to allocate audio codec context");
- goto exit_1;
- }
- }
-
- // Make sure index seems correct
- if(src->vstream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid video stream index: %d", src->vstream_idx);
- goto exit_2;
- } else if(src->vstream_idx >= 0) {
- // Find video decoder
- vcodec = avcodec_find_decoder(format_ctx->streams[src->vstream_idx]->codec->codec_id);
- if(!vcodec) {
- Kit_SetError("No suitable video decoder found");
- goto exit_2;
- }
-
- // Copy the original video codec context
- vcodec_ctx = avcodec_alloc_context3(vcodec);
- if(avcodec_copy_context(vcodec_ctx, format_ctx->streams[src->vstream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy video codec context");
- goto exit_2;
- }
-
- // Create a video decoder context
- if(avcodec_open2(vcodec_ctx, vcodec, NULL) < 0) {
- Kit_SetError("Unable to allocate video codec context");
- goto exit_3;
- }
- }
-
- if(src->sstream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid subtitle stream index: %d", src->sstream_idx);
- goto exit_2;
- } else if(src->sstream_idx >= 0) {
- // Find subtitle decoder
- scodec = avcodec_find_decoder(format_ctx->streams[src->sstream_idx]->codec->codec_id);
- if(!scodec) {
- Kit_SetError("No suitable subtitle decoder found");
- goto exit_4;
- }
-
- // Copy the original subtitle codec context
- scodec_ctx = avcodec_alloc_context3(scodec);
- if(avcodec_copy_context(scodec_ctx, format_ctx->streams[src->sstream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy subtitle codec context");
- goto exit_4;
- }
-
- // Create a subtitle decoder context
- if(avcodec_open2(scodec_ctx, scodec, NULL) < 0) {
- Kit_SetError("Unable to allocate subtitle codec context");
- goto exit_5;
- }
- }
-
- player->acodec_ctx = acodec_ctx;
- player->vcodec_ctx = vcodec_ctx;
- player->scodec_ctx = scodec_ctx;
- player->src = src;
- return 0;
-
-exit_5:
- avcodec_free_context(&scodec_ctx);
-exit_4:
- avcodec_close(vcodec_ctx);
-exit_3:
- avcodec_free_context(&vcodec_ctx);
-exit_2:
- avcodec_close(acodec_ctx);
-exit_1:
- avcodec_free_context(&acodec_ctx);
-exit_0:
- return 1;
-}
-
-static int reset_libass_track(Kit_Player *player) {
- AVCodecContext *scodec_ctx = player->scodec_ctx;
-
- if(scodec_ctx == NULL) {
- return 0;
- }
-
- // Flush libass track events
- ass_flush_events(player->ass_track);
- return 0;
-}
+#include <SDL.h>
-static void _FindPixelFormat(enum AVPixelFormat fmt, unsigned int *out_fmt) {
- switch(fmt) {
- case AV_PIX_FMT_YUV420P9:
- case AV_PIX_FMT_YUV420P10:
- case AV_PIX_FMT_YUV420P12:
- case AV_PIX_FMT_YUV420P14:
- case AV_PIX_FMT_YUV420P16:
- case AV_PIX_FMT_YUV420P:
- *out_fmt = SDL_PIXELFORMAT_YV12;
- break;
- case AV_PIX_FMT_YUYV422:
- *out_fmt = SDL_PIXELFORMAT_YUY2;
- break;
- case AV_PIX_FMT_UYVY422:
- *out_fmt = SDL_PIXELFORMAT_UYVY;
- break;
- default:
- *out_fmt = SDL_PIXELFORMAT_ABGR8888;
- break;
- }
-}
-
-static void _FindAudioFormat(enum AVSampleFormat fmt, int *bytes, bool *is_signed, unsigned int *format) {
- switch(fmt) {
- case AV_SAMPLE_FMT_U8:
- *bytes = 1;
- *is_signed = false;
- *format = AUDIO_U8;
- break;
- case AV_SAMPLE_FMT_S16:
- *bytes = 2;
- *is_signed = true;
- *format = AUDIO_S16SYS;
- break;
- case AV_SAMPLE_FMT_S32:
- *bytes = 4;
- *is_signed = true;
- *format = AUDIO_S32SYS;
- break;
- default:
- *bytes = 2;
- *is_signed = true;
- *format = AUDIO_S16SYS;
- break;
- }
-}
-
-static enum AVPixelFormat _FindAVPixelFormat(unsigned int fmt) {
- switch(fmt) {
- case SDL_PIXELFORMAT_IYUV: return AV_PIX_FMT_YUV420P;
- case SDL_PIXELFORMAT_YV12: return AV_PIX_FMT_YUV420P;
- case SDL_PIXELFORMAT_YUY2: return AV_PIX_FMT_YUYV422;
- case SDL_PIXELFORMAT_UYVY: return AV_PIX_FMT_UYVY422;
- case SDL_PIXELFORMAT_ARGB8888: return AV_PIX_FMT_BGRA;
- case SDL_PIXELFORMAT_ABGR8888: return AV_PIX_FMT_RGBA;
- default:
- return AV_PIX_FMT_NONE;
- }
-}
-
-static enum AVSampleFormat _FindAVSampleFormat(int format) {
- switch(format) {
- case AUDIO_U8: return AV_SAMPLE_FMT_U8;
- case AUDIO_S16SYS: return AV_SAMPLE_FMT_S16;
- case AUDIO_S32SYS: return AV_SAMPLE_FMT_S32;
- default:
- return AV_SAMPLE_FMT_NONE;
- }
-}
-
-static unsigned int _FindAVChannelLayout(int channels) {
- switch(channels) {
- case 1: return AV_CH_LAYOUT_MONO;
- case 2: return AV_CH_LAYOUT_STEREO;
- case 4: return AV_CH_LAYOUT_QUAD;
- case 6: return AV_CH_LAYOUT_5POINT1;
- default: return AV_CH_LAYOUT_STEREO_DOWNMIX;
- }
-}
-
-static Kit_VideoPacket* _CreateVideoPacket(AVFrame *frame, double pts) {
- Kit_VideoPacket *p = calloc(1, sizeof(Kit_VideoPacket));
- p->frame = frame;
- p->pts = pts;
- return p;
-}
-
-static void _FreeVideoPacket(void *ptr) {
- Kit_VideoPacket *packet = ptr;
- av_freep(&packet->frame->data[0]);
- av_frame_free(&packet->frame);
- free(packet);
-}
-
-static Kit_AudioPacket* _CreateAudioPacket(const char* data, size_t len, double pts) {
- Kit_AudioPacket *p = calloc(1, sizeof(Kit_AudioPacket));
- p->rb = Kit_CreateRingBuffer(len);
- Kit_WriteRingBuffer(p->rb, data, len);
- p->pts = pts;
- return p;
-}
-
-static void _FreeAudioPacket(void *ptr) {
- Kit_AudioPacket *packet = ptr;
- Kit_DestroyRingBuffer(packet->rb);
- free(packet);
-}
-
-static Kit_ControlPacket* _CreateControlPacket(Kit_ControlPacketType type, double value1) {
- Kit_ControlPacket *p = calloc(1, sizeof(Kit_ControlPacket));
- p->type = type;
- p->value1 = value1;
- return p;
-}
-
-static void _FreeControlPacket(void *ptr) {
- Kit_ControlPacket *packet = ptr;
- free(packet);
-}
-
-
-static Kit_SubtitlePacket* _CreateSubtitlePacket(double pts_start, double pts_end, SDL_Rect *rect, SDL_Surface *surface) {
- Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket));
- p->pts_start = pts_start;
- p->pts_end = pts_end;
- p->surface = surface;
- p->rect = rect;
- p->texture = NULL; // Cached texture
- return p;
-}
-
-static void _FreeSubtitlePacket(void *ptr) {
- Kit_SubtitlePacket *packet = ptr;
- SDL_FreeSurface(packet->surface);
- if(packet->texture) {
- SDL_DestroyTexture(packet->texture);
- }
- free(packet->rect);
- free(packet);
-}
-
-static double _GetSystemTime() {
- return (double)av_gettime() / 1000000.0;
-}
-
-static void _HandleVideoPacket(Kit_Player *player, AVPacket *packet) {
- assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- AVCodecContext *vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- AVFrame *iframe = player->tmp_vframe;
-
- while(packet->size > 0) {
- int len = avcodec_decode_video2(vcodec_ctx, player->tmp_vframe, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- // Target frame
- AVFrame *oframe = av_frame_alloc();
- av_image_alloc(
- oframe->data,
- oframe->linesize,
- vcodec_ctx->width,
- vcodec_ctx->height,
- _FindAVPixelFormat(player->vformat.format),
- 1);
-
- // Scale from source format to target format, don't touch the size
- sws_scale(
- (struct SwsContext *)player->sws,
- (const unsigned char * const *)iframe->data,
- iframe->linesize,
- 0,
- vcodec_ctx->height,
- oframe->data,
- oframe->linesize);
-
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = av_frame_get_best_effort_timestamp(player->tmp_vframe);
- pts *= av_q2d(fmt_ctx->streams[player->src->vstream_idx]->time_base);
- }
-
- // Just seeked, set sync clock & pos.
- if(player->seek_flag == 1) {
- player->vclock_pos = pts;
- player->clock_sync = _GetSystemTime() - pts;
- player->seek_flag = 0;
- }
-
- // Lock, write to audio buffer, unlock
- Kit_VideoPacket *vpacket = _CreateVideoPacket(oframe, pts);
- bool done = false;
- if(SDL_LockMutex(player->vmutex) == 0) {
- if(Kit_WriteBuffer((Kit_Buffer*)player->vbuffer, vpacket) == 0) {
- done = true;
- }
- SDL_UnlockMutex(player->vmutex);
- }
-
- // Unable to write packet, free it.
- if(!done) {
- _FreeVideoPacket(vpacket);
- }
- }
- packet->size -= len;
- packet->data += len;
- }
-}
+#include "kitchensink/kitplayer.h"
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/video/kitvideo.h"
+#include "kitchensink/internal/audio/kitaudio.h"
+#include "kitchensink/internal/subtitle/kitsubtitle.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+enum DecoderIndex {
+ KIT_VIDEO_DEC = 0,
+ KIT_AUDIO_DEC,
+ KIT_SUBTITLE_DEC,
+ KIT_DEC_COUNT
+};
-static void _HandleAudioPacket(Kit_Player *player, AVPacket *packet) {
+// Return 0 if stream is good but nothing else to do for now
+// Return -1 if there may still work to be done
+// Return 1 if there was an error or stream end
+static int _DemuxStream(const Kit_Player *player) {
assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- int len, len2;
- int dst_linesize;
- int dst_nb_samples, dst_bufsize;
- unsigned char **dst_data;
- AVCodecContext *acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- struct SwrContext *swr = (struct SwrContext *)player->swr;
- AVFrame *aframe = (AVFrame*)player->tmp_aframe;
-
- while(packet->size > 0) {
- len = avcodec_decode_audio4(acodec_ctx, aframe, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- dst_nb_samples = av_rescale_rnd(
- aframe->nb_samples,
- player->aformat.samplerate,
- acodec_ctx->sample_rate,
- AV_ROUND_UP);
-
- av_samples_alloc_array_and_samples(
- &dst_data,
- &dst_linesize,
- player->aformat.channels,
- dst_nb_samples,
- _FindAVSampleFormat(player->aformat.format),
- 0);
-
- len2 = swr_convert(
- swr,
- dst_data,
- aframe->nb_samples,
- (const unsigned char **)aframe->extended_data,
- aframe->nb_samples);
-
- dst_bufsize = av_samples_get_buffer_size(
- &dst_linesize,
- player->aformat.channels,
- len2,
- _FindAVSampleFormat(player->aformat.format), 1);
-
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = av_frame_get_best_effort_timestamp(player->tmp_aframe);
- pts *= av_q2d(fmt_ctx->streams[player->src->astream_idx]->time_base);
- }
-
- // Just seeked, set sync clock & pos.
- if(player->seek_flag == 1) {
- player->vclock_pos = pts;
- player->clock_sync = _GetSystemTime() - pts;
- player->seek_flag = 0;
- }
-
- // Lock, write to audio buffer, unlock
- Kit_AudioPacket *apacket = _CreateAudioPacket((char*)dst_data[0], (size_t)dst_bufsize, pts);
- bool done = false;
- if(SDL_LockMutex(player->amutex) == 0) {
- if(Kit_WriteBuffer((Kit_Buffer*)player->abuffer, apacket) == 0) {
- done = true;
- }
- SDL_UnlockMutex(player->amutex);
- }
-
- // Couldn't write packet, free memory
- if(!done) {
- _FreeAudioPacket(apacket);
- }
-
- av_freep(&dst_data[0]);
- av_freep(&dst_data);
- }
+ AVFormatContext *format_ctx = player->src->format_ctx;
- packet->size -= len;
- packet->data += len;
- }
-}
-
-static void _HandleBitmapSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub, AVSubtitleRect *rect) {
- if(rect->nb_colors == 256) {
- // Paletted image based subtitles. Convert and set palette.
- SDL_Surface *s = SDL_CreateRGBSurfaceFrom(
- rect->data[0],
- rect->w, rect->h, 8,
- rect->linesize[0],
- 0, 0, 0, 0);
-
- SDL_SetPaletteColors(s->format->palette, (SDL_Color*)rect->data[1], 0, 256);
-
- Uint32 rmask, gmask, bmask, amask;
- #if SDL_BYTEORDER == SDL_BIG_ENDIAN
- rmask = 0xff000000;
- gmask = 0x00ff0000;
- bmask = 0x0000ff00;
- amask = 0x000000ff;
- #else
- rmask = 0x000000ff;
- gmask = 0x0000ff00;
- bmask = 0x00ff0000;
- amask = 0xff000000;
- #endif
- SDL_Surface *tmp = SDL_CreateRGBSurface(
- 0, rect->w, rect->h, 32,
- rmask, gmask, bmask, amask);
- SDL_BlitSurface(s, NULL, tmp, NULL);
- SDL_FreeSurface(s);
-
- SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect));
- dst_rect->x = rect->x;
- dst_rect->y = rect->y;
- dst_rect->w = rect->w;
- dst_rect->h = rect->h;
-
- double start = pts + (sub->start_display_time / 1000.0f);
- double end = -1;
- if(sub->end_display_time < UINT_MAX) {
- end = pts + (sub->end_display_time / 1000.0f);
- }
-
- spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp);
- }
-}
-
-static void _ProcessAssSubtitleRect(Kit_Player *player, AVSubtitleRect *rect) {
- ass_process_data((ASS_Track*)player->ass_track, rect->ass, strlen(rect->ass));
-}
-
-static void _ProcessAssImage(SDL_Surface *surface, const ASS_Image *img) {
- int x, y;
- // libass headers claim img->color is RGBA, but the alpha is 0.
- unsigned char r = ((img->color) >> 24) & 0xFF;
- unsigned char g = ((img->color) >> 16) & 0xFF;
- unsigned char b = ((img->color) >> 8) & 0xFF;
- unsigned char *src = img->bitmap;
- unsigned char *dst = (unsigned char*)surface->pixels;
-
- for(y = 0; y < img->h; y++) {
- for(x = 0; x < img->w; x++) {
- dst[x * 4 + 0] = r;
- dst[x * 4 + 1] = g;
- dst[x * 4 + 2] = b;
- dst[x * 4 + 3] = src[x];
- }
- src += img->stride;
- dst += surface->pitch;
- }
-}
-
-static void _HandleAssSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub) {
- double start = pts + (sub->start_display_time / 1000.0f);
- double end = pts + (sub->end_display_time / 1000.0f);
-
- // Process current chunk of data
- unsigned int now = start * 1000;
- int change = 0;
- ASS_Image *images = ass_render_frame((ASS_Renderer*)player->ass_renderer, (ASS_Track*)player->ass_track, now, &change);
-
- // Convert to SDL_Surfaces
- if(change > 0) {
- ASS_Image *now = images;
- if(now != NULL) {
- do {
- Uint32 rmask, gmask, bmask, amask;
- #if SDL_BYTEORDER == SDL_BIG_ENDIAN
- rmask = 0xff000000;
- gmask = 0x00ff0000;
- bmask = 0x0000ff00;
- amask = 0x000000ff;
- #else
- rmask = 0x000000ff;
- gmask = 0x0000ff00;
- bmask = 0x00ff0000;
- amask = 0xff000000;
- #endif
- SDL_Surface *tmp = SDL_CreateRGBSurface(
- 0, now->w, now->h, 32,
- rmask, gmask, bmask, amask);
-
- _ProcessAssImage(tmp, now);
-
- SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect));
- dst_rect->x = now->dst_x;
- dst_rect->y = now->dst_y;
- dst_rect->w = now->w;
- dst_rect->h = now->h;
-
- spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp);
- } while((now = now->next) != NULL);
- }
+ // If any buffer is full, just stop here for now.
+ // Since we don't know what kind of data is going to come out of av_read_frame, we really
+ // want to make sure we are prepared for everything :)
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_Decoder *dec = player->decoders[i];
+ if(dec == NULL)
+ continue;
+ if(!Kit_CanWriteDecoderInput(dec))
+ return 0;
}
-}
-
-static void _HandleSubtitlePacket(Kit_Player *player, AVPacket *packet) {
- assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- int len;
- AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- Kit_SubtitlePacket *tmp = NULL;
- unsigned int it;
- AVSubtitle sub;
- memset(&sub, 0, sizeof(AVSubtitle));
-
- if(packet->size > 0) {
- len = avcodec_decode_subtitle2(scodec_ctx, &sub, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = packet->pts;
- pts *= av_q2d(fmt_ctx->streams[player->src->sstream_idx]->time_base);
- }
- // Convert subtitles to SDL_Surface and create a packet
- Kit_SubtitlePacket *spackets[KIT_SBUFFERSIZE];
- memset(spackets, 0, sizeof(Kit_SubtitlePacket*) * KIT_SBUFFERSIZE);
-
- int n = 0;
- bool has_ass = false;
- for(int r = 0; r < sub.num_rects; r++) {
- switch(sub.rects[r]->type) {
- case SUBTITLE_BITMAP:
- _HandleBitmapSubtitle(spackets, &n, player, pts, &sub, sub.rects[r]);
- break;
- case SUBTITLE_ASS:
- _ProcessAssSubtitleRect(player, sub.rects[r]);
- has_ass = true;
- break;
- case SUBTITLE_TEXT:
- break;
- case SUBTITLE_NONE:
- break;
- }
- }
-
- // Process libass content
- if(has_ass) {
- _HandleAssSubtitle(spackets, &n, player, pts, &sub);
- }
-
- // Lock, write to subtitle buffer, unlock
- if(SDL_LockMutex(player->smutex) == 0) {
- if(has_ass) {
- Kit_ClearList((Kit_List*)player->sbuffer);
- } else {
- // Clear out old subtitles that should only be valid until next (this) subtitle
- it = 0;
- while((tmp = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(tmp->pts_end < 0) {
- Kit_RemoveFromList((Kit_List*)player->sbuffer, it);
- }
- }
- }
-
- // Add new subtitle
- for(int i = 0; i < KIT_SBUFFERSIZE; i++) {
- Kit_SubtitlePacket *spacket = spackets[i];
- if(spacket != NULL) {
- if(Kit_WriteList((Kit_List*)player->sbuffer, spacket) == 0) {
- spackets[i] = NULL;
- }
- }
- }
-
- // Unlock subtitle buffer
- SDL_UnlockMutex(player->smutex);
- }
-
- // Couldn't write packet, free memory
- for(int i = 0; i < KIT_SBUFFERSIZE; i++) {
- if(spackets[i] != NULL) {
- _FreeSubtitlePacket(spackets[i]);
- }
- }
- }
+ // Attempt to read frame. Just return here if it fails.
+ AVPacket *packet = av_packet_alloc();
+ if(av_read_frame(format_ctx, packet) < 0) {
+ av_packet_free(&packet);
+ return 1;
}
-}
-static void _HandlePacket(Kit_Player *player, AVPacket *packet) {
// Check if this is a packet we need to handle and pass it on
- if(player->vcodec_ctx != NULL && packet->stream_index == player->src->vstream_idx) {
- _HandleVideoPacket(player, packet);
- }
- else if(player->acodec_ctx != NULL && packet->stream_index == player->src->astream_idx) {
- _HandleAudioPacket(player, packet);
- }
- else if(player->scodec_ctx != NULL && packet->stream_index == player->src->sstream_idx) {
- _HandleSubtitlePacket(player, packet);
- }
-}
-
-static void _HandleFlushCommand(Kit_Player *player, Kit_ControlPacket *packet) {
- if(player->abuffer != NULL) {
- if(SDL_LockMutex(player->amutex) == 0) {
- Kit_ClearBuffer((Kit_Buffer*)player->abuffer);
- SDL_UnlockMutex(player->amutex);
- }
- }
- if(player->vbuffer != NULL) {
- if(SDL_LockMutex(player->vmutex) == 0) {
- Kit_ClearBuffer((Kit_Buffer*)player->vbuffer);
- SDL_UnlockMutex(player->vmutex);
- }
- }
- if(player->sbuffer != NULL) {
- if(SDL_LockMutex(player->smutex) == 0) {
- Kit_ClearList((Kit_List*)player->sbuffer);
- SDL_UnlockMutex(player->smutex);
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_Decoder *dec = player->decoders[i];
+ if(dec == NULL)
+ continue;
+ if(dec->stream_index == packet->stream_index) {
+ Kit_WriteDecoderInput(player->decoders[i], packet);
+ return -1;
}
}
- reset_libass_track(player);
-}
-static void _HandleSeekCommand(Kit_Player *player, Kit_ControlPacket *packet) {
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
-
- // Find and limit absolute position
- double seek = packet->value1;
- double duration = Kit_GetPlayerDuration(player);
- if(player->vclock_pos + seek <= 0) {
- seek = -player->vclock_pos;
- }
- if(player->vclock_pos + seek >= duration) {
- seek = duration - player->vclock_pos;
- }
- double absolute_pos = player->vclock_pos + seek;
- int64_t seek_target = absolute_pos * AV_TIME_BASE;
-
- // Seek to timestamp.
- avformat_seek_file(fmt_ctx, -1, INT64_MIN, seek_target, INT64_MAX, 0);
- if(player->vcodec_ctx != NULL)
- avcodec_flush_buffers(player->vcodec_ctx);
- if(player->acodec_ctx != NULL)
- avcodec_flush_buffers(player->acodec_ctx);
-
- // On first packet, set clock and current position
- player->seek_flag = 1;
+ // We only get here if packet was not written to a decoder. IF that is the case,
+ // disregard and free the packet.
+ av_packet_free(&packet);
+ return -1;
}
-static void _HandleControlPacket(Kit_Player *player, Kit_ControlPacket *packet) {
- switch(packet->type) {
- case KIT_CONTROL_FLUSH:
- _HandleFlushCommand(player, packet);
- break;
- case KIT_CONTROL_SEEK:
- _HandleSeekCommand(player, packet);
- break;
+static bool _IsOutputEmpty(const Kit_Player *player) {
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_Decoder *dec = player->decoders[i];
+ if(dec == NULL)
+ continue;
+ if(Kit_PeekDecoderOutput(dec))
+ return false;
}
+ return true;
}
-// Return 0 if stream is good but nothing else to do for now
-// Return -1 if there is still work to be done
-// Return 1 if there was an error or stream end
-static int _UpdatePlayer(Kit_Player *player) {
- assert(player != NULL);
-
- AVFormatContext *format_ctx = (AVFormatContext*)player->src->format_ctx;
+static int _RunDecoder(Kit_Player *player) {
+ int got;
+ int ret = 0;
- // Handle control queue
- if(SDL_LockMutex(player->cmutex) == 0) {
- Kit_ControlPacket *cpacket;
- while((cpacket = (Kit_ControlPacket*)Kit_ReadBuffer(player->cbuffer)) != NULL) {
- _HandleControlPacket(player, cpacket);
- _FreeControlPacket(cpacket);
- }
- SDL_UnlockMutex(player->cmutex);
+ if(SDL_LockMutex(player->dec_lock) != 0) {
+ return ret;
}
- // If either buffer is full, just stop here for now.
- // Since we don't know what kind of data is going to come out of av_read_frame, we really
- // want to make sure we are prepared for everything :)
- if(player->vcodec_ctx != NULL) {
- if(SDL_LockMutex(player->vmutex) == 0) {
- int ret = Kit_IsBufferFull(player->vbuffer);
- SDL_UnlockMutex(player->vmutex);
- if(ret == 1) {
- return 0;
- }
- }
- }
- if(player->acodec_ctx != NULL) {
- if(SDL_LockMutex(player->amutex) == 0) {
- int ret = Kit_IsBufferFull(player->abuffer);
- SDL_UnlockMutex(player->amutex);
- if(ret == 1) {
- return 0;
- }
- }
+ while((got = _DemuxStream(player)) == -1);
+ if(got == 1 && _IsOutputEmpty(player)) {
+ ret = 1;
+ goto exit;
}
- // Attempt to read frame. Just return here if it fails.
- AVPacket packet;
- if(av_read_frame(format_ctx, &packet) < 0) {
- return 1;
+ // Run decoders for a bit
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ while(Kit_RunDecoder(player->decoders[i]) == 1);
}
- _HandlePacket(player, &packet);
- av_packet_unref(&packet);
- return -1;
+
+exit:
+ SDL_UnlockMutex(player->dec_lock);
+ return ret;
}
static int _DecoderThread(void *ptr) {
- Kit_Player *player = (Kit_Player*)ptr;
+ Kit_Player *player = ptr;
bool is_running = true;
bool is_playing = true;
- int ret;
while(is_running) {
if(player->state == KIT_CLOSED) {
@@ -842,424 +117,142 @@ static int _DecoderThread(void *ptr) {
is_playing = false;
continue;
}
-
- // Get more data from demuxer, decode. Wait a bit if there's no more work for now.
- ret = _UpdatePlayer(player);
- if(ret == 1) {
+ if(_RunDecoder(player) == 1) {
player->state = KIT_STOPPED;
- } else if(ret == 0) {
- SDL_Delay(1);
+ continue;
}
+ SDL_Delay(2);
}
// Just idle while waiting for work.
- SDL_Delay(10);
+ SDL_Delay(25);
}
return 0;
}
-static const char * const font_mime[] = {
- "application/x-font-ttf",
- "application/x-font-truetype",
- "application/x-truetype-font",
- "application/x-font-opentype",
- "application/vnd.ms-opentype",
- "application/font-sfnt",
- NULL
-};
-
-static bool attachment_is_font(AVStream *stream) {
- AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE);
- if(tag) {
- for(int n = 0; font_mime[n]; n++) {
- if(av_strcasecmp(font_mime[n], tag->value) == 0) {
- return true;
- }
- }
- }
- return false;
-}
-
-Kit_Player* Kit_CreatePlayer(const Kit_Source *src) {
+Kit_Player* Kit_CreatePlayer(const Kit_Source *src,
+ int video_stream_index,
+ int audio_stream_index,
+ int subtitle_stream_index,
+ int screen_w,
+ int screen_h) {
assert(src != NULL);
+ assert(screen_w >= 0);
+ assert(screen_h >= 0);
+
+ if(video_stream_index < 0 && subtitle_stream_index >= 0) {
+ Kit_SetError("Subtitle stream selected without video stream");
+ goto exit_0;
+ }
Kit_Player *player = calloc(1, sizeof(Kit_Player));
if(player == NULL) {
Kit_SetError("Unable to allocate player");
- return NULL;
- }
-
- AVCodecContext *acodec_ctx = NULL;
- AVCodecContext *vcodec_ctx = NULL;
- AVCodecContext *scodec_ctx = NULL;
-
- // Initialize codecs
- if(_InitCodecs(player, src) != 0) {
- goto error;
- }
-
- // Init audio codec information if audio codec is initialized
- acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- if(acodec_ctx != NULL) {
- player->aformat.samplerate = acodec_ctx->sample_rate;
- player->aformat.channels = acodec_ctx->channels > 2 ? 2 : acodec_ctx->channels;
- player->aformat.is_enabled = true;
- player->aformat.stream_idx = src->astream_idx;
- _FindAudioFormat(acodec_ctx->sample_fmt, &player->aformat.bytes, &player->aformat.is_signed, &player->aformat.format);
-
- player->swr = swr_alloc_set_opts(
- NULL,
- _FindAVChannelLayout(player->aformat.channels), // Target channel layout
- _FindAVSampleFormat(player->aformat.format), // Target fmt
- player->aformat.samplerate, // Target samplerate
- acodec_ctx->channel_layout, // Source channel layout
- acodec_ctx->sample_fmt, // Source fmt
- acodec_ctx->sample_rate, // Source samplerate
- 0, NULL);
- if(swr_init((struct SwrContext *)player->swr) != 0) {
- Kit_SetError("Unable to initialize audio converter context");
- goto error;
- }
-
- player->abuffer = Kit_CreateBuffer(KIT_ABUFFERSIZE, _FreeAudioPacket);
- if(player->abuffer == NULL) {
- Kit_SetError("Unable to initialize audio ringbuffer");
- goto error;
- }
-
- player->tmp_aframe = av_frame_alloc();
- if(player->tmp_aframe == NULL) {
- Kit_SetError("Unable to initialize temporary audio frame");
- goto error;
- }
- }
-
- // Initialize video codec information is initialized
- vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- if(vcodec_ctx != NULL) {
- player->vformat.is_enabled = true;
- player->vformat.width = vcodec_ctx->width;
- player->vformat.height = vcodec_ctx->height;
- player->vformat.stream_idx = src->vstream_idx;
- _FindPixelFormat(vcodec_ctx->pix_fmt, &player->vformat.format);
-
- player->sws = sws_getContext(
- vcodec_ctx->width, // Source w
- vcodec_ctx->height, // Source h
- vcodec_ctx->pix_fmt, // Source fmt
- vcodec_ctx->width, // Target w
- vcodec_ctx->height, // Target h
- _FindAVPixelFormat(player->vformat.format), // Target fmt
- SWS_BICUBIC,
- NULL, NULL, NULL);
- if((struct SwsContext *)player->sws == NULL) {
- Kit_SetError("Unable to initialize video converter context");
- goto error;
- }
-
- player->vbuffer = Kit_CreateBuffer(KIT_VBUFFERSIZE, _FreeVideoPacket);
- if(player->vbuffer == NULL) {
- Kit_SetError("Unable to initialize video ringbuffer");
- goto error;
- }
-
- player->tmp_vframe = av_frame_alloc();
- if(player->tmp_vframe == NULL) {
- Kit_SetError("Unable to initialize temporary video frame");
- goto error;
- }
- }
-
- // Initialize subtitle codec
- scodec_ctx = (AVCodecContext*)player->scodec_ctx;
- if(scodec_ctx != NULL) {
- player->sformat.is_enabled = true;
- player->sformat.stream_idx = src->sstream_idx;
-
- // subtitle packet buffer
- player->sbuffer = Kit_CreateList(KIT_SBUFFERSIZE, _FreeSubtitlePacket);
- if(player->sbuffer == NULL) {
- Kit_SetError("Unable to initialize active subtitle list");
- goto error;
- }
-
- // Initialize libass renderer
- Kit_LibraryState *state = Kit_GetLibraryState();
- player->ass_renderer = ass_renderer_init(state->libass_handle);
- if(player->ass_renderer == NULL) {
- Kit_SetError("Unable to initialize libass renderer");
- goto error;
- }
-
- // Read fonts from attachment streams and give them to libass
- AVFormatContext *format_ctx = player->src->format_ctx;
- for (int j = 0; j < format_ctx->nb_streams; j++) {
- AVStream *st = format_ctx->streams[j];
- if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) {
- const AVDictionaryEntry *tag = av_dict_get(
- st->metadata,
- "filename",
- NULL,
- AV_DICT_MATCH_CASE);
- if(tag) {
- ass_add_font(
- state->libass_handle,
- tag->value,
- (char*)st->codec->extradata,
- st->codec->extradata_size);
- }
- }
- }
-
- // Init libass fonts and window frame size
- ass_set_fonts(player->ass_renderer, NULL, "sans-serif", ASS_FONTPROVIDER_AUTODETECT, NULL, 1);
- ass_set_frame_size(player->ass_renderer, vcodec_ctx->width, vcodec_ctx->height);
- ass_set_hinting(player->ass_renderer, ASS_HINTING_NONE);
-
- // Initialize libass track
- player->ass_track = ass_new_track(state->libass_handle);
- if(player->ass_track == NULL) {
- Kit_SetError("Unable to initialize libass track");
- goto error;
- }
-
- // Set up libass track headers (ffmpeg provides these)
- if(scodec_ctx->subtitle_header) {
- ass_process_codec_private(
- (ASS_Track*)player->ass_track,
- (char*)scodec_ctx->subtitle_header,
- scodec_ctx->subtitle_header_size);
- }
- }
-
- player->cbuffer = Kit_CreateBuffer(KIT_CBUFFERSIZE, _FreeControlPacket);
- if(player->cbuffer == NULL) {
- Kit_SetError("Unable to initialize control ringbuffer");
- goto error;
+ goto exit_0;
}
- player->vmutex = SDL_CreateMutex();
- if(player->vmutex == NULL) {
- Kit_SetError("Unable to allocate video mutex");
- goto error;
+ // Initialize audio decoder
+ player->decoders[KIT_AUDIO_DEC] = Kit_CreateAudioDecoder(src, audio_stream_index);
+ if(player->decoders[KIT_AUDIO_DEC] == NULL && audio_stream_index >= 0) {
+ goto exit_1;
}
- player->amutex = SDL_CreateMutex();
- if(player->amutex == NULL) {
- Kit_SetError("Unable to allocate audio mutex");
- goto error;
+ // Initialize video decoder
+ player->decoders[KIT_VIDEO_DEC] = Kit_CreateVideoDecoder(src, video_stream_index);
+ if(player->decoders[KIT_VIDEO_DEC] == NULL && video_stream_index >= 0) {
+ goto exit_2;
}
- player->cmutex = SDL_CreateMutex();
- if(player->cmutex == NULL) {
- Kit_SetError("Unable to allocate control buffer mutex");
- goto error;
+ // Initialize subtitle decoder.
+ Kit_OutputFormat output;
+ Kit_GetDecoderOutputFormat(player->decoders[KIT_VIDEO_DEC], &output);
+ player->decoders[KIT_SUBTITLE_DEC] = Kit_CreateSubtitleDecoder(
+ src, subtitle_stream_index, output.width, output.height, screen_w, screen_h);
+ if(player->decoders[KIT_SUBTITLE_DEC] == NULL && subtitle_stream_index >= 0) {
+ goto exit_2;
}
- player->smutex = SDL_CreateMutex();
- if(player->smutex == NULL) {
- Kit_SetError("Unable to allocate subtitle buffer mutex");
- goto error;
+ // Decoder thread lock
+ player->dec_lock = SDL_CreateMutex();
+ if(player->dec_lock == NULL) {
+ Kit_SetError("Unable to create a decoder thread lock mutex: %s", SDL_GetError());
+ goto exit_2;
}
+ // Decoder thread
player->dec_thread = SDL_CreateThread(_DecoderThread, "Kit Decoder Thread", player);
if(player->dec_thread == NULL) {
Kit_SetError("Unable to create a decoder thread: %s", SDL_GetError());
- goto error;
+ goto exit_3;
}
+ player->src = src;
return player;
-error:
- if(player->amutex != NULL) {
- SDL_DestroyMutex(player->amutex);
- }
- if(player->vmutex != NULL) {
- SDL_DestroyMutex(player->vmutex);
- }
- if(player->cmutex != NULL) {
- SDL_DestroyMutex(player->cmutex);
- }
- if(player->smutex != NULL) {
- SDL_DestroyMutex(player->smutex);
- }
- if(player->tmp_aframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_aframe);
- }
- if(player->tmp_vframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_vframe);
- }
-
- Kit_DestroyBuffer((Kit_Buffer*)player->vbuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->abuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->cbuffer);
- Kit_DestroyList((Kit_List*)player->sbuffer);
-
- if(player->sws != NULL) {
- sws_freeContext((struct SwsContext *)player->sws);
- }
- if(player->swr != NULL) {
- swr_free((struct SwrContext **)player->swr);
- }
-
- if(player->ass_track != NULL) {
- ass_free_track((ASS_Track*)player->ass_track);
- }
- if(player->ass_renderer != NULL) {
- ass_renderer_done((ASS_Renderer *)player->ass_renderer);
- }
- if(player != NULL) {
- free(player);
+exit_3:
+ SDL_DestroyMutex(player->dec_lock);
+exit_2:
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_CloseDecoder(player->decoders[i]);
}
+exit_1:
+ free(player);
+exit_0:
return NULL;
}
void Kit_ClosePlayer(Kit_Player *player) {
if(player == NULL) return;
- // Kill the decoder thread
- player->state = KIT_CLOSED;
- SDL_WaitThread(player->dec_thread, NULL);
- SDL_DestroyMutex(player->vmutex);
- SDL_DestroyMutex(player->amutex);
- SDL_DestroyMutex(player->cmutex);
- SDL_DestroyMutex(player->smutex);
-
- // Free up converters
- if(player->sws != NULL) {
- sws_freeContext((struct SwsContext *)player->sws);
- }
- if(player->swr != NULL) {
- swr_free((struct SwrContext **)&player->swr);
- }
-
- // Free temporary frames
- if(player->tmp_vframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_vframe);
- }
- if(player->tmp_aframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_aframe);
+ // Kill the decoder thread and mutex
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ player->state = KIT_CLOSED;
+ SDL_UnlockMutex(player->dec_lock);
}
+ SDL_WaitThread(player->dec_thread, NULL);
+ SDL_DestroyMutex(player->dec_lock);
- // Free contexts
- avcodec_close((AVCodecContext*)player->acodec_ctx);
- avcodec_close((AVCodecContext*)player->vcodec_ctx);
- avcodec_close((AVCodecContext*)player->scodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->acodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->vcodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->scodec_ctx);
-
- // Free local audio buffers
- Kit_DestroyBuffer((Kit_Buffer*)player->cbuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->abuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->vbuffer);
- Kit_DestroyList((Kit_List*)player->sbuffer);
-
- // Free libass context
- if(player->ass_track != NULL) {
- ass_free_track((ASS_Track*)player->ass_track);
- }
- if(player->ass_renderer != NULL) {
- ass_renderer_done((ASS_Renderer *)player->ass_renderer);
+ // Shutdown decoders
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_CloseDecoder(player->decoders[i]);
}
// Free the player structure itself
free(player);
}
-int Kit_GetVideoData(Kit_Player *player, SDL_Texture *texture) {
+void Kit_SetPlayerScreenSize(Kit_Player *player, int w, int h) {
assert(player != NULL);
+ Kit_Decoder *dec = player->decoders[KIT_SUBTITLE_DEC];
+ if(dec == NULL)
+ return;
+ Kit_SetSubtitleDecoderSize(dec, w, h);
+}
- if(player->src->vstream_idx == -1) {
- return 0;
- }
-
- assert(texture != NULL);
-
- // If paused or stopped, do nothing
- if(player->state == KIT_PAUSED) {
- return 0;
- }
- if(player->state == KIT_STOPPED) {
- return 0;
- }
-
- // Read a packet from buffer, if one exists. Stop here if not.
- Kit_VideoPacket *packet = NULL;
- Kit_VideoPacket *n_packet = NULL;
- if(SDL_LockMutex(player->vmutex) == 0) {
- packet = (Kit_VideoPacket*)Kit_PeekBuffer((Kit_Buffer*)player->vbuffer);
- if(packet == NULL) {
- SDL_UnlockMutex(player->vmutex);
- return 0;
- }
-
- // Print some data
- double cur_video_ts = _GetSystemTime() - player->clock_sync;
-
- // Check if we want the packet
- if(packet->pts > cur_video_ts + VIDEO_SYNC_THRESHOLD) {
- // Video is ahead, don't show yet.
- SDL_UnlockMutex(player->vmutex);
- return 0;
- } else if(packet->pts < cur_video_ts - VIDEO_SYNC_THRESHOLD) {
- // Video is lagging, skip until we find a good PTS to continue from.
- while(packet != NULL) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->vbuffer);
- n_packet = (Kit_VideoPacket*)Kit_PeekBuffer((Kit_Buffer*)player->vbuffer);
- if(n_packet == NULL) {
- break;
- }
- _FreeVideoPacket(packet);
- packet = n_packet;
- if(packet->pts > cur_video_ts - VIDEO_SYNC_THRESHOLD) {
- break;
- }
- }
- }
-
- // Advance buffer one frame forwards
- Kit_AdvanceBuffer((Kit_Buffer*)player->vbuffer);
- player->vclock_pos = packet->pts;
-
- // Update textures as required. Handle UYV frames separately.
- if(player->vformat.format == SDL_PIXELFORMAT_YV12
- || player->vformat.format == SDL_PIXELFORMAT_IYUV)
- {
- SDL_UpdateYUVTexture(
- texture, NULL,
- packet->frame->data[0], packet->frame->linesize[0],
- packet->frame->data[1], packet->frame->linesize[1],
- packet->frame->data[2], packet->frame->linesize[2]);
- }
- else {
- SDL_UpdateTexture(
- texture, NULL,
- packet->frame->data[0],
- packet->frame->linesize[0]);
- }
+int Kit_GetPlayerVideoStream(const Kit_Player *player) {
+ assert(player != NULL);
+ return Kit_GetDecoderStreamIndex(player->decoders[KIT_VIDEO_DEC]);
+}
- _FreeVideoPacket(packet);
- SDL_UnlockMutex(player->vmutex);
- } else {
- Kit_SetError("Unable to lock video buffer mutex");
- return 1;
- }
+int Kit_GetPlayerAudioStream(const Kit_Player *player) {
+ assert(player != NULL);
+ return Kit_GetDecoderStreamIndex(player->decoders[KIT_AUDIO_DEC]);
+}
- return 0;
+int Kit_GetPlayerSubtitleStream(const Kit_Player *player) {
+ assert(player != NULL);
+ return Kit_GetDecoderStreamIndex(player->decoders[KIT_SUBTITLE_DEC]);
}
-int Kit_GetSubtitleData(Kit_Player *player, SDL_Renderer *renderer) {
+int Kit_GetPlayerVideoData(Kit_Player *player, SDL_Texture *texture) {
assert(player != NULL);
- // If there is no audio stream, don't bother.
- if(player->src->sstream_idx == -1) {
+ Kit_Decoder *dec = player->decoders[KIT_VIDEO_DEC];
+ if(dec == NULL) {
return 0;
}
- assert(renderer != NULL);
-
// If paused or stopped, do nothing
if(player->state == KIT_PAUSED) {
return 0;
@@ -1268,47 +261,15 @@ int Kit_GetSubtitleData(Kit_Player *player, SDL_Renderer *renderer) {
return 0;
}
- unsigned int it;
- Kit_SubtitlePacket *packet = NULL;
-
- // Current sync timestamp
- double cur_subtitle_ts = _GetSystemTime() - player->clock_sync;
-
- // Read a packet from buffer, if one exists. Stop here if not.
- if(SDL_LockMutex(player->smutex) == 0) {
- // Check if refresh is required and remove old subtitles
- it = 0;
- while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(packet->pts_end >= 0 && packet->pts_end < cur_subtitle_ts) {
- Kit_RemoveFromList((Kit_List*)player->sbuffer, it);
- }
- }
-
- // Render subtitle bitmaps
- it = 0;
- while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(packet->texture == NULL) {
- packet->texture = SDL_CreateTextureFromSurface(renderer, packet->surface);
- SDL_SetTextureBlendMode(packet->texture, SDL_BLENDMODE_BLEND);
- }
- SDL_RenderCopy(renderer, packet->texture, NULL, packet->rect);
- }
-
- // Unlock subtitle buffer mutex.
- SDL_UnlockMutex(player->smutex);
- } else {
- Kit_SetError("Unable to lock subtitle buffer mutex");
- return 0;
- }
-
- return 0;
+ return Kit_GetVideoDecoderData(dec, texture);
}
-int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int cur_buf_len) {
+int Kit_GetPlayerAudioData(Kit_Player *player, unsigned char *buffer, int length) {
assert(player != NULL);
+ assert(buffer != NULL);
- // If there is no audio stream, don't bother.
- if(player->src->astream_idx == -1) {
+ Kit_Decoder *dec = player->decoders[KIT_AUDIO_DEC];
+ if(dec == NULL) {
return 0;
}
@@ -1317,8 +278,6 @@ int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int
return 0;
}
- assert(buffer != NULL);
-
// If paused or stopped, do nothing
if(player->state == KIT_PAUSED) {
return 0;
@@ -1327,158 +286,147 @@ int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int
return 0;
}
- // Read a packet from buffer, if one exists. Stop here if not.
- int ret = 0;
- Kit_AudioPacket *packet = NULL;
- Kit_AudioPacket *n_packet = NULL;
- if(SDL_LockMutex(player->amutex) == 0) {
- packet = (Kit_AudioPacket*)Kit_PeekBuffer((Kit_Buffer*)player->abuffer);
- if(packet == NULL) {
- SDL_UnlockMutex(player->amutex);
- return 0;
- }
+ return Kit_GetAudioDecoderData(dec, buffer, length);
+}
- int bytes_per_sample = player->aformat.bytes * player->aformat.channels;
- double bps = bytes_per_sample * player->aformat.samplerate;
- double cur_audio_ts = _GetSystemTime() - player->clock_sync + ((double)cur_buf_len / bps);
- double diff = cur_audio_ts - packet->pts;
- int diff_samples = fabs(diff) * player->aformat.samplerate;
-
- if(packet->pts > cur_audio_ts + AUDIO_SYNC_THRESHOLD) {
- // Audio is ahead, fill buffer with some silence
- int max_diff_samples = length / bytes_per_sample;
- int max_samples = (max_diff_samples < diff_samples) ? max_diff_samples : diff_samples;
-
- av_samples_set_silence(
- &buffer,
- 0, // Offset
- max_samples,
- player->aformat.channels,
- _FindAVSampleFormat(player->aformat.format));
-
- int diff_bytes = max_samples * bytes_per_sample;
-
- SDL_UnlockMutex(player->amutex);
- return diff_bytes;
-
- } else if(packet->pts < cur_audio_ts - AUDIO_SYNC_THRESHOLD) {
- // Audio is lagging, skip until good pts is found
-
- while(1) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->abuffer);
- n_packet = (Kit_AudioPacket*)Kit_PeekBuffer((Kit_Buffer*)player->abuffer);
- if(n_packet != NULL) {
- packet = n_packet;
- } else {
- break;
- }
- if(packet->pts > cur_audio_ts - AUDIO_SYNC_THRESHOLD) {
- break;
- }
- }
- }
+int Kit_GetPlayerSubtitleData(Kit_Player *player, SDL_Texture *texture, SDL_Rect *sources, SDL_Rect *targets, int limit) {
+ assert(player != NULL);
+ assert(texture != NULL);
+ assert(sources != NULL);
+ assert(targets != NULL);
+ assert(limit >= 0);
- if(length > 0) {
- ret = Kit_ReadRingBuffer(packet->rb, (char*)buffer, length);
- }
+ Kit_Decoder *dec = player->decoders[KIT_SUBTITLE_DEC];
+ if(dec == NULL) {
+ return 0;
+ }
- if(Kit_GetRingBufferLength(packet->rb) == 0) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->abuffer);
- _FreeAudioPacket(packet);
- } else {
- double adjust = (double)ret / bps;
- packet->pts += adjust;
- }
+ // If paused, just return the current items
+ if(player->state == KIT_PAUSED) {
+ return Kit_GetSubtitleDecoderInfo(dec, texture, sources, targets, limit);
+ }
- SDL_UnlockMutex(player->amutex);
- } else {
- Kit_SetError("Unable to lock audio buffer mutex");
+ // If stopped, do nothing.
+ if(player->state == KIT_STOPPED) {
return 0;
}
- return ret;
+ // Refresh texture, then refresh rects and return number of items in the texture.
+ Kit_GetSubtitleDecoderTexture(dec, texture);
+ return Kit_GetSubtitleDecoderInfo(dec, texture, sources, targets, limit);
}
void Kit_GetPlayerInfo(const Kit_Player *player, Kit_PlayerInfo *info) {
assert(player != NULL);
assert(info != NULL);
- AVCodecContext *acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- AVCodecContext *vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx;
-
- // Reset everything to 0. We might not fill all fields.
- memset(info, 0, sizeof(Kit_PlayerInfo));
-
- if(acodec_ctx != NULL) {
- strncpy(info->acodec, acodec_ctx->codec->name, KIT_CODECMAX-1);
- strncpy(info->acodec_name, acodec_ctx->codec->long_name, KIT_CODECNAMEMAX-1);
- memcpy(&info->audio, &player->aformat, sizeof(Kit_AudioFormat));
+ void *streams[] = {&info->video, &info->audio, &info->subtitle};
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_Decoder *dec = player->decoders[i];
+ Kit_PlayerStreamInfo *stream = streams[i];
+ Kit_GetDecoderCodecInfo(dec, &stream->codec);
+ Kit_GetDecoderOutputFormat(dec, &stream->output);
}
- if(vcodec_ctx != NULL) {
- strncpy(info->vcodec, vcodec_ctx->codec->name, KIT_CODECMAX-1);
- strncpy(info->vcodec_name, vcodec_ctx->codec->long_name, KIT_CODECNAMEMAX-1);
- memcpy(&info->video, &player->vformat, sizeof(Kit_VideoFormat));
+}
+
+static void _SetClockSync(Kit_Player *player) {
+ double sync = _GetSystemTime();
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_SetDecoderClockSync(player->decoders[i], sync);
}
- if(scodec_ctx != NULL) {
- strncpy(info->scodec, scodec_ctx->codec->name, KIT_CODECMAX-1);
- strncpy(info->scodec_name, scodec_ctx->codec->long_name, KIT_CODECNAMEMAX-1);
- memcpy(&info->subtitle, &player->sformat, sizeof(Kit_SubtitleFormat));
+}
+
+static void _ChangeClockSync(Kit_Player *player, double delta) {
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_ChangeDecoderClockSync(player->decoders[i], delta);
}
}
Kit_PlayerState Kit_GetPlayerState(const Kit_Player *player) {
assert(player != NULL);
-
return player->state;
}
void Kit_PlayerPlay(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state == KIT_PLAYING) {
- return;
- }
- if(player->state == KIT_STOPPED) {
- player->clock_sync = _GetSystemTime();
+ double tmp;
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ switch(player->state) {
+ case KIT_PLAYING:
+ case KIT_CLOSED:
+ break;
+ case KIT_PAUSED:
+ tmp = _GetSystemTime() - player->pause_started;
+ _ChangeClockSync(player, tmp);
+ player->state = KIT_PLAYING;
+ break;
+ case KIT_STOPPED:
+ _SetClockSync(player);
+ player->state = KIT_PLAYING;
+ break;
+ }
+ SDL_UnlockMutex(player->dec_lock);
}
- if(player->state == KIT_PAUSED) {
- player->clock_sync += _GetSystemTime() - player->pause_start;
- }
- player->state = KIT_PLAYING;
}
void Kit_PlayerStop(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state == KIT_STOPPED) {
- return;
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ switch(player->state) {
+ case KIT_STOPPED:
+ case KIT_CLOSED:
+ break;
+ case KIT_PLAYING:
+ case KIT_PAUSED:
+ player->state = KIT_STOPPED;
+ break;
+ }
+ SDL_UnlockMutex(player->dec_lock);
}
- player->state = KIT_STOPPED;
}
void Kit_PlayerPause(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state != KIT_PLAYING) {
- return;
- }
- player->pause_start = _GetSystemTime();
player->state = KIT_PAUSED;
+ player->pause_started = _GetSystemTime();
}
-int Kit_PlayerSeek(Kit_Player *player, double m_time) {
+int Kit_PlayerSeek(Kit_Player *player, double seek_set) {
assert(player != NULL);
+ double position;
+ double duration;
+ int64_t seek_target;
+ int flags = AVSEEK_FLAG_ANY;
- // Send packets to control stream
- if(SDL_LockMutex(player->cmutex) == 0) {
- // Flush audio and video buffers, then set seek, then unlock control queue mutex.
- Kit_WriteBuffer((Kit_Buffer*)player->cbuffer, _CreateControlPacket(KIT_CONTROL_FLUSH, 0));
- Kit_WriteBuffer((Kit_Buffer*)player->cbuffer, _CreateControlPacket(KIT_CONTROL_SEEK, m_time));
- SDL_UnlockMutex(player->cmutex);
- } else {
- Kit_SetError("Unable to lock control queue mutex");
- return 1;
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ duration = Kit_GetPlayerDuration(player);
+ position = Kit_GetPlayerPosition(player);
+ if(seek_set <= 0) {
+ seek_set = 0;
+ }
+ if(seek_set >= duration) {
+ seek_set = duration;
+ }
+
+ // Set source to timestamp
+ AVFormatContext *format_ctx = player->src->format_ctx;
+ seek_target = seek_set * AV_TIME_BASE;
+ if(seek_set < position) {
+ flags |= AVSEEK_FLAG_BACKWARD;
+ }
+ if(avformat_seek_file(format_ctx, -1, 0, seek_target, seek_target, flags) < 0) {
+ Kit_SetError("Unable to seek source");
+ SDL_UnlockMutex(player->dec_lock);
+ return 1;
+ } else {
+ _ChangeClockSync(player, position - seek_set);
+ for(int i = 0; i < KIT_DEC_COUNT; i++) {
+ Kit_ClearDecoderBuffers(player->decoders[i]);
+ }
+ }
+
+ // That's it. Unlock and continue.
+ SDL_UnlockMutex(player->dec_lock);
}
return 0;
@@ -1487,12 +435,18 @@ int Kit_PlayerSeek(Kit_Player *player, double m_time) {
double Kit_GetPlayerDuration(const Kit_Player *player) {
assert(player != NULL);
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
+ AVFormatContext *fmt_ctx = player->src->format_ctx;
return (fmt_ctx->duration / AV_TIME_BASE);
}
double Kit_GetPlayerPosition(const Kit_Player *player) {
assert(player != NULL);
- return player->vclock_pos;
+ if(player->decoders[KIT_VIDEO_DEC]) {
+ return ((Kit_Decoder*)player->decoders[KIT_VIDEO_DEC])->clock_pos;
+ }
+ if(player->decoders[KIT_AUDIO_DEC]) {
+ return ((Kit_Decoder*)player->decoders[KIT_AUDIO_DEC])->clock_pos;
+ }
+ return 0;
}
diff --git a/src/kitsource.c b/src/kitsource.c
index 075bcbd..588f829 100644
--- a/src/kitsource.c
+++ b/src/kitsource.c
@@ -1,13 +1,26 @@
-#include "kitchensink/kitsource.h"
-#include "kitchensink/kiterror.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
+#include "kitchensink/kitsource.h"
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+#define AVIO_BUF_SIZE 32768
+
+static int _ScanSource(AVFormatContext *format_ctx) {
+ av_opt_set_int(format_ctx, "probesize", INT_MAX, 0);
+ av_opt_set_int(format_ctx, "analyzeduration", INT_MAX, 0);
+ if(avformat_find_stream_info(format_ctx, NULL) < 0) {
+ Kit_SetError("Unable to fetch source information");
+ return 1;
+ }
+ return 0;
+}
Kit_Source* Kit_CreateSourceFromUrl(const char *url) {
assert(url != NULL);
@@ -24,19 +37,11 @@ Kit_Source* Kit_CreateSourceFromUrl(const char *url) {
goto exit_0;
}
- av_opt_set_int(src->format_ctx, "probesize", INT_MAX, 0);
- av_opt_set_int(src->format_ctx, "analyzeduration", INT_MAX, 0);
-
- // Fetch stream information. This may potentially take a while.
- if(avformat_find_stream_info((AVFormatContext *)src->format_ctx, NULL) < 0) {
- Kit_SetError("Unable to fetch source information");
+ // Scan source information (may seek forwards)
+ if(_ScanSource(src->format_ctx)) {
goto exit_1;
}
- // Find best streams for defaults
- src->astream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO);
- src->vstream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO);
- src->sstream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE);
return src;
exit_1:
@@ -46,13 +51,122 @@ exit_0:
return NULL;
}
+Kit_Source* Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallback seek_cb, void *userdata) {
+ assert(read_cb != NULL);
+
+ Kit_Source *src = calloc(1, sizeof(Kit_Source));
+ if(src == NULL) {
+ Kit_SetError("Unable to allocate source");
+ return NULL;
+ }
+
+ uint8_t *avio_buf = av_malloc(AVIO_BUF_SIZE);
+ if(avio_buf == NULL) {
+ Kit_SetError("Unable to allocate avio buffer");
+ goto exit_0;
+ }
+
+ AVFormatContext *format_ctx = avformat_alloc_context();
+ if(format_ctx == NULL) {
+ Kit_SetError("Unable to allocate format context");
+ goto exit_1;
+ }
+
+ AVIOContext *avio_ctx = avio_alloc_context(
+ avio_buf, AVIO_BUF_SIZE, 0, userdata, read_cb, 0, seek_cb);
+ if(avio_ctx == NULL) {
+ Kit_SetError("Unable to allocate avio context");
+ goto exit_2;
+ }
+
+ // Set the format as AVIO format
+ format_ctx->pb = avio_ctx;
+
+ // Attempt to open source
+ if(avformat_open_input(&format_ctx, "", NULL, NULL) < 0) {
+ Kit_SetError("Unable to open custom source");
+ goto exit_3;
+ }
+
+ // Scan source information (may seek forwards)
+ if(_ScanSource(format_ctx)) {
+ goto exit_4;
+ }
+
+ // Set internals
+ src->format_ctx = format_ctx;
+ src->avio_ctx = avio_ctx;
+ return src;
+
+exit_4:
+ avformat_close_input(&format_ctx);
+exit_3:
+ av_freep(&avio_ctx);
+exit_2:
+ avformat_free_context(format_ctx);
+exit_1:
+ av_freep(&avio_buf);
+exit_0:
+ free(src);
+ return NULL;
+}
+
+static int _RWReadCallback(void *userdata, uint8_t *buf, int size) {
+ return SDL_RWread((SDL_RWops*)userdata, buf, 1, size);
+}
+
+static int64_t _RWGetSize(SDL_RWops *rw_ops) {
+ int64_t current_pos;
+ int64_t max_pos;
+
+ // First, see if tell works at all, and fail with -1 if it doesn't.
+ current_pos = SDL_RWtell(rw_ops);
+ if(current_pos < 0) {
+ return -1;
+ }
+
+ // Seek to end, get pos (this is the size), then return.
+ if(SDL_RWseek(rw_ops, 0, RW_SEEK_END) < 0) {
+ return -1; // Seek failed, never mind then
+ }
+ max_pos = SDL_RWtell(rw_ops);
+ SDL_RWseek(rw_ops, current_pos, RW_SEEK_SET);
+ return max_pos;
+}
+
+static int64_t _RWSeekCallback(void *userdata, int64_t offset, int whence) {
+ int rw_whence = 0;
+ if(whence & AVSEEK_SIZE)
+ return _RWGetSize(userdata);
+
+ if((whence & ~AVSEEK_FORCE) == SEEK_CUR)
+ rw_whence = RW_SEEK_CUR;
+ else if((whence & ~AVSEEK_FORCE) == SEEK_SET)
+ rw_whence = RW_SEEK_SET;
+ else if((whence & ~AVSEEK_FORCE) == SEEK_END)
+ rw_whence = RW_SEEK_END;
+
+ return SDL_RWseek((SDL_RWops*)userdata, offset, rw_whence);
+}
+
+
+Kit_Source* Kit_CreateSourceFromRW(SDL_RWops *rw_ops) {
+ return Kit_CreateSourceFromCustom(_RWReadCallback, _RWSeekCallback, rw_ops);
+}
+
void Kit_CloseSource(Kit_Source *src) {
assert(src != NULL);
- avformat_close_input((AVFormatContext **)&src->format_ctx);
+ AVFormatContext *format_ctx = src->format_ctx;
+ AVIOContext *avio_ctx = src->avio_ctx;
+ avformat_close_input(&format_ctx);
+ if(avio_ctx) {
+ av_freep(&avio_ctx->buffer);
+ av_freep(&avio_ctx);
+ }
free(src);
}
-int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_StreamInfo *info, int index) {
+int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_SourceStreamInfo *info, int index) {
assert(src != NULL);
assert(info != NULL);
@@ -99,31 +213,6 @@ int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_StreamType type) {
return ret;
}
-int Kit_SetSourceStream(Kit_Source *src, const Kit_StreamType type, int index) {
- assert(src != NULL);
- switch(type) {
- case KIT_STREAMTYPE_AUDIO: src->astream_idx = index; break;
- case KIT_STREAMTYPE_VIDEO: src->vstream_idx = index; break;
- case KIT_STREAMTYPE_SUBTITLE: src->sstream_idx = index; break;
- default:
- Kit_SetError("Invalid stream type");
- return 1;
- }
- return 0;
-}
-
-int Kit_GetSourceStream(const Kit_Source *src, const Kit_StreamType type) {
- assert(src != NULL);
- switch(type) {
- case KIT_STREAMTYPE_AUDIO: return src->astream_idx;
- case KIT_STREAMTYPE_VIDEO: return src->vstream_idx;
- case KIT_STREAMTYPE_SUBTITLE: return src->sstream_idx;
- default:
- break;
- }
- return -1;
-}
-
int Kit_GetSourceStreamCount(const Kit_Source *src) {
assert(src != NULL);
return ((AVFormatContext *)src->format_ctx)->nb_streams;
diff --git a/src/kitutils.c b/src/kitutils.c
index b618c07..8cf7261 100644
--- a/src/kitutils.c
+++ b/src/kitutils.c
@@ -1,8 +1,8 @@
+#include <SDL.h>
+
#include "kitchensink/kitutils.h"
#include "kitchensink/kitsource.h"
-#include <SDL2/SDL.h>
-
const char* Kit_GetSDLAudioFormatString(unsigned int type) {
switch(type) {
case AUDIO_S8: return "AUDIO_S8";
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
deleted file mode 100644
index ddfe4f9..0000000
--- a/tests/CMakeLists.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-find_package(CUnit)
-
-add_executable(test_lib
- test_lib.c
- test_source.c
-)
-
-include_directories(${CUNIT_INCLUDE_DIR} . ../include/)
-if(MINGW)
- target_link_libraries(test_lib mingw32)
-endif()
-target_link_libraries(test_lib
- SDL_kitchensink_static
- ${CUNIT_LIBRARIES}
- ${SDL2_LIBRARIES}
- ${FFMPEG_LIBRARIES}
-)
-add_custom_target(unittest test_lib)
diff --git a/tests/data/CEP140_512kb.mp4 b/tests/data/CEP140_512kb.mp4
deleted file mode 100644
index 4341032..0000000
--- a/tests/data/CEP140_512kb.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/test_lib.c b/tests/test_lib.c
deleted file mode 100644
index 92eb058..0000000
--- a/tests/test_lib.c
+++ /dev/null
@@ -1,28 +0,0 @@
-#include <CUnit/CUnit.h>
-#include <CUnit/Basic.h>
-#include "kitchensink/kitchensink.h"
-
-void source_test_suite(CU_pSuite suite);
-
-int main(int argc, char **argv) {
- CU_pSuite suite = NULL;
-
- Kit_Init(KIT_INIT_NETWORK|KIT_INIT_FORMATS);
-
- if(CU_initialize_registry() != CUE_SUCCESS) {
- return CU_get_error();
- }
-
- suite = CU_add_suite("Source functions", NULL, NULL);
- if(suite == NULL) goto end;
- source_test_suite(suite);
-
- // Run tests
- CU_basic_set_mode(CU_BRM_VERBOSE);
- CU_basic_run_tests();
-
-end:
- CU_cleanup_registry();
- Kit_Quit();
- return CU_get_error();
-}
diff --git a/tests/test_source.c b/tests/test_source.c
deleted file mode 100644
index 0f70635..0000000
--- a/tests/test_source.c
+++ /dev/null
@@ -1,48 +0,0 @@
-#include <CUnit/CUnit.h>
-#include <CUnit/Basic.h>
-#include <kitchensink/kitchensink.h>
-
-Kit_Source *src = NULL;
-
-void test_Kit_CreateSourceFromUrl(void) {
- CU_ASSERT_PTR_NULL(Kit_CreateSourceFromUrl("nonexistent"));
- src = Kit_CreateSourceFromUrl("../../tests/data/CEP140_512kb.mp4");
- CU_ASSERT_PTR_NOT_NULL(src);
-}
-
-void test_Kit_GetBestSourceStream(void) {
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO) == 0);
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO) == 1);
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_UNKNOWN) == -1);
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_DATA) == -1);
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_ATTACHMENT) == -1);
- CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE) == -1);
-}
-
-void test_Kit_GetSourceStreamCount(void) {
- CU_ASSERT(Kit_GetSourceStreamCount(src) == 2);
-}
-
-void test_Kit_SetSourceStream(void) {
- CU_ASSERT(Kit_SetSourceStream(src, KIT_STREAMTYPE_VIDEO, 0) == 0);
- CU_ASSERT(Kit_SetSourceStream(src, KIT_STREAMTYPE_UNKNOWN, 0) == 1);
-}
-
-void test_Kit_GetSourceStream(void) {
- CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_VIDEO) == 0);
- CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_AUDIO) == 1);
- CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_UNKNOWN) == -1);
-}
-
-void test_Kit_CloseSource(void) {
- Kit_CloseSource(src);
-}
-
-void source_test_suite(CU_pSuite suite) {
- if(CU_add_test(suite, "Kit_CreateSourceFromUrl", test_Kit_CreateSourceFromUrl) == NULL) { return; }
- if(CU_add_test(suite, "Kit_GetBestSourceStream", test_Kit_GetBestSourceStream) == NULL) { return; }
- if(CU_add_test(suite, "Kit_GetSourceStreamCount", test_Kit_GetSourceStreamCount) == NULL) { return; }
- if(CU_add_test(suite, "Kit_SetSourceStream", test_Kit_SetSourceStream) == NULL) { return; }
- if(CU_add_test(suite, "Kit_GetSourceStream", test_Kit_GetSourceStream) == NULL) { return; }
- if(CU_add_test(suite, "Kit_CloseSource", test_Kit_CloseSource) == NULL) { return; }
-}
diff --git a/travis/ffmpeg.sh b/travis/ffmpeg.sh
deleted file mode 100644
index d27c136..0000000
--- a/travis/ffmpeg.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-
-if [ ! -e "$HOME/local/lib/libavcodec.so" ]; then
- wget https://www.ffmpeg.org/releases/ffmpeg-3.0.4.tar.gz -O ~/ffmpeg.tar.gz
- tar xzf ~/ffmpeg.tar.gz -C ~/
- cd ~/ffmpeg-3.0.4
- export CC=gcc-5
- ./configure --prefix=$HOME/local --disable-static --enable-shared --disable-doc
- make
- make install
-else
- echo 'Using cached FFmpeg build directory.';
-fi
diff --git a/travis/sdl2.sh b/travis/sdl2.sh
deleted file mode 100644
index 8b914fb..0000000
--- a/travis/sdl2.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-
-if [ ! -e "$HOME/local/lib/libSDL2.so" ]; then
- wget https://www.libsdl.org/release/SDL2-2.0.4.tar.gz -O ~/SDL2.tar.gz
- tar -xzvf ~/SDL2.tar.gz -C ~/
- mkdir ~/sdl-build
- cd ~/sdl-build
- export CC=gcc-5
- ~/SDL2-2.0.4/configure --prefix=$HOME/local
- make -j2
- make install
-else
- echo 'Using cached SDL2 build directory.';
-fi