├── doxygen-custom.css ├── include └── kitchensink2 │ ├── internal │ ├── kitbufferindex.h │ ├── video │ │ ├── kitvideoutils.h │ │ └── kitvideo.h │ ├── subtitle │ │ ├── renderers │ │ │ ├── kitsubimage.h │ │ │ ├── kitsubass.h │ │ │ └── kitsubrenderer.h │ │ ├── kitsubtitlepacket.h │ │ ├── kitsubtitle.h │ │ └── kitatlas.h │ ├── utils │ │ ├── kitlog.h │ │ └── kithelpers.h │ ├── audio │ │ ├── kitaudioutils.h │ │ └── kitaudio.h │ ├── kittimer.h │ ├── kitlibstate.h │ ├── kitdecoderthread.h │ ├── kitdemuxerthread.h │ ├── kitdemuxer.h │ ├── kitpacketbuffer.h │ ├── libass.h │ └── kitdecoder.h │ ├── kitchensink.h │ ├── kitcodec.h │ ├── kitconfig.h │ ├── kiterror.h │ ├── kitutils.h │ ├── kitformat.h │ ├── kitlib.h │ └── kitsource.h ├── pkg-config.pc.in ├── .gitignore ├── .clang-tidy ├── src ├── kitformat.c ├── internal │ ├── kitlibstate.c │ ├── utils │ │ └── kithelpers.c │ ├── subtitle │ │ ├── kitsubtitlepacket.c │ │ ├── renderers │ │ │ ├── kitsubrenderer.c │ │ │ ├── kitsubass.c │ │ │ └── kitsubimage.c │ │ ├── kitatlas.c │ │ └── kitsubtitle.c │ ├── audio │ │ └── kitaudioutils.c │ ├── libass.c │ ├── kittimer.c │ ├── kitdemuxerthread.c │ ├── video │ │ └── kitvideoutils.c │ ├── kitdecoderthread.c │ ├── kitdemuxer.c │ ├── kitpacketbuffer.c │ └── kitdecoder.c ├── kiterror.c ├── kitlib.c ├── kitutils.c └── kitsource.c ├── .clang-format ├── SECURITY.md ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── ci.yml ├── cmake ├── Findass.cmake └── Findffmpeg.cmake ├── LICENSE ├── examples ├── example_audio.c ├── example_rawdump.c ├── example_simple.c ├── example_rwops.c └── example_custom.c ├── CMakeLists.txt └── README.md /doxygen-custom.css: -------------------------------------------------------------------------------- 1 | .fragment { 2 | padding: 0.5em !important; 3 | } 4 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitbufferindex.h: -------------------------------------------------------------------------------- 1 | #ifndef KITBUFFERINDEX 2 | #define KITBUFFERINDEX 3 | 4 | typedef enum KitBufferIndex 5 | { 6 | KIT_VIDEO_INDEX = 0, 7 | KIT_AUDIO_INDEX, 8 | KIT_SUBTITLE_INDEX, 9 | KIT_INDEX_COUNT 10 | } Kit_BufferIndex; 11 | 12 | #endif // KITBUFFERINDEX 13 | -------------------------------------------------------------------------------- /pkg-config.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=${prefix} 3 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 4 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 5 | 6 | Name: SDL_kitchensink 7 | Description: SDL2/ffmpeg video playback library 8 | Version: @KIT_VERSION@ 9 | URL: https://github.com/katajakasa/SDL_kitchensink 10 | 11 | Libs: -L${libdir} -l@PROJECT_NAME@ 12 | Cflags: -I${includedir} 13 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/video/kitvideoutils.h: -------------------------------------------------------------------------------- 1 | #ifndef KITVIDEOUTILS_H 2 | #define KITVIDEOUTILS_H 3 | 4 | #include "kitchensink2/kitformat.h" 5 | 6 | enum AVPixelFormat Kit_FindBestAVPixelFormat(enum AVPixelFormat fmt); 7 | unsigned int Kit_FindSDLPixelFormat(enum AVPixelFormat fmt); 8 | enum AVPixelFormat Kit_FindAVPixelFormat(unsigned int fmt); 9 | Kit_HardwareDeviceType Kit_FindHWDeviceType(enum AVHWDeviceType type); 10 | 11 | #endif // KITVIDEOUTILS_H 12 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/renderers/kitsubimage.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSUBIMAGE_H 2 | #define KITSUBIMAGE_H 3 | 4 | #include "kitchensink2/internal/kitdecoder.h" 5 | #include "kitchensink2/internal/subtitle/renderers/kitsubrenderer.h" 6 | #include "kitchensink2/kitconfig.h" 7 | 8 | KIT_LOCAL Kit_SubtitleRenderer * 9 | Kit_CreateImageSubtitleRenderer(Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h); 10 | 11 | #endif // KITSUBIMAGE_H 12 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/utils/kitlog.h: -------------------------------------------------------------------------------- 1 | #ifndef KITLOG_H 2 | #define KITLOG_H 3 | 4 | #ifdef NDEBUG 5 | #define LOG(...) 6 | #else 7 | #include 8 | #define LOG(...) \ 9 | fprintf(stderr, __VA_ARGS__); \ 10 | fflush(stderr) 11 | #endif 12 | 13 | #endif // KITLOG_H 14 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/renderers/kitsubass.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSUBASS_H 2 | #define KITSUBASS_H 3 | 4 | #include "kitchensink2/internal/kitdecoder.h" 5 | #include "kitchensink2/internal/subtitle/renderers/kitsubrenderer.h" 6 | #include "kitchensink2/kitconfig.h" 7 | 8 | KIT_LOCAL Kit_SubtitleRenderer *Kit_CreateASSSubtitleRenderer( 9 | const AVFormatContext *format_ctx, Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h 10 | ); 11 | 12 | #endif // KITSUBASS_H 13 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/utils/kithelpers.h: -------------------------------------------------------------------------------- 1 | #ifndef KITHELPERS_H 2 | #define KITHELPERS_H 3 | 4 | #include "kitchensink2/kitconfig.h" 5 | #include 6 | #include 7 | 8 | KIT_LOCAL double Kit_GetSystemTime(); 9 | KIT_LOCAL bool Kit_StreamIsFontAttachment(const AVStream *stream); 10 | 11 | KIT_LOCAL int Kit_max(int a, int b); 12 | KIT_LOCAL int Kit_min(int a, int b); 13 | KIT_LOCAL int Kit_clamp(int v, int min, int max); 14 | 15 | #endif // KITHELPERS_H 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | 34 | # VSCode 35 | .vscode 36 | 37 | # CLion 38 | .idea 39 | cmake-build-* 40 | 41 | # Other 42 | build/ 43 | docs/ -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | clang-analyzer-*, 5 | clang-diagnostic-*, 6 | performance-*, 7 | -clang-analyzer-cplusplus*, 8 | -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, 9 | WarningsAsErrors: '*' 10 | HeaderFilterRegex: '' 11 | FormatStyle: llvm 12 | CheckOptions: 13 | - key: google-readability-braces-around-statements.ShortStatementLines 14 | value: '1' 15 | - key: google-readability-function-size.StatementThreshold 16 | value: '800' 17 | ... 18 | -------------------------------------------------------------------------------- /include/kitchensink2/kitchensink.h: -------------------------------------------------------------------------------- 1 | #ifndef KITCHENSINK_H 2 | #define KITCHENSINK_H 3 | 4 | /** 5 | * @brief Header aggregator 6 | * 7 | * @file kitchensink.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-27 10 | */ 11 | 12 | #include "kitchensink2/kitcodec.h" 13 | #include "kitchensink2/kitconfig.h" 14 | #include "kitchensink2/kiterror.h" 15 | #include "kitchensink2/kitformat.h" 16 | #include "kitchensink2/kitlib.h" 17 | #include "kitchensink2/kitplayer.h" 18 | #include "kitchensink2/kitsource.h" 19 | #include "kitchensink2/kitutils.h" 20 | 21 | #endif // KITCHENSINK_H 22 | -------------------------------------------------------------------------------- /src/kitformat.c: -------------------------------------------------------------------------------- 1 | #include "kitchensink2/kitformat.h" 2 | 3 | #include 4 | 5 | void Kit_ResetVideoFormatRequest(Kit_VideoFormatRequest *request) { 6 | request->hw_device_types = KIT_HWDEVICE_TYPE_ALL; 7 | request->format = SDL_PIXELFORMAT_UNKNOWN; 8 | request->width = -1; 9 | request->height = -1; 10 | } 11 | 12 | void Kit_ResetAudioFormatRequest(Kit_AudioFormatRequest *request) { 13 | request->format = 0; 14 | request->is_signed = -1; 15 | request->bytes = -1; 16 | request->channels = -1; 17 | request->sample_rate = -1; 18 | } 19 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/audio/kitaudioutils.h: -------------------------------------------------------------------------------- 1 | #ifndef KITAUDIOUTILS_H 2 | #define KITAUDIOUTILS_H 3 | 4 | #include "kitchensink2/kitconfig.h" 5 | #include 6 | 7 | KIT_LOCAL enum AVSampleFormat Kit_FindAVSampleFormat(int format); 8 | KIT_LOCAL void Kit_FindAVChannelLayout(int channels, AVChannelLayout *layout); 9 | KIT_LOCAL int Kit_FindChannelLayout(const AVChannelLayout *channel_layout); 10 | KIT_LOCAL int Kit_FindBytes(enum AVSampleFormat fmt); 11 | KIT_LOCAL int Kit_FindSDLSampleFormat(enum AVSampleFormat fmt); 12 | KIT_LOCAL int Kit_FindSignedness(enum AVSampleFormat fmt); 13 | 14 | #endif // KITAUDIOUTILS_H 15 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | ColumnLimit: 119 5 | IndentWidth: 4 6 | SpaceBeforeParens: Never 7 | BreakBeforeBraces: Custom 8 | BraceWrapping: 9 | AfterControlStatement: 'false' 10 | AfterFunction: false 11 | AfterStruct: false 12 | AfterUnion: false 13 | BeforeElse: false 14 | IndentBraces: false 15 | AfterEnum: true 16 | AllowShortFunctionsOnASingleLine: 'None' 17 | AllowShortBlocksOnASingleLine: 'Never' 18 | AlignArrayOfStructures: Left 19 | IndentCaseLabels: true 20 | AllowShortEnumsOnASingleLine: false 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | AlignAfterOpenBracket: BlockIndent 24 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/audio/kitaudio.h: -------------------------------------------------------------------------------- 1 | #ifndef KITAUDIO_H 2 | #define KITAUDIO_H 3 | 4 | #include "kitchensink2/internal/kitdecoder.h" 5 | #include "kitchensink2/internal/kittimer.h" 6 | #include "kitchensink2/kitconfig.h" 7 | #include "kitchensink2/kitsource.h" 8 | 9 | KIT_LOCAL Kit_Decoder *Kit_CreateAudioDecoder( 10 | const Kit_Source *src, const Kit_AudioFormatRequest *format_request, Kit_Timer *sync_timer, int stream_index 11 | ); 12 | KIT_LOCAL int Kit_GetAudioDecoderData(Kit_Decoder *dec, size_t backend_buffer_size, unsigned char *buf, size_t len); 13 | KIT_LOCAL int Kit_GetAudioDecoderOutputFormat(const Kit_Decoder *dec, Kit_AudioOutputFormat *output); 14 | 15 | #endif // KITAUDIO_H 16 | -------------------------------------------------------------------------------- /src/internal/kitlibstate.c: -------------------------------------------------------------------------------- 1 | #include "kitchensink2/internal/kitlibstate.h" 2 | #include 3 | 4 | static Kit_LibraryState _library_state = { 5 | .init_flags = 0, 6 | .thread_count = 0, 7 | .font_hinting = 0, 8 | .video_packet_buffer_size = 16, 9 | .audio_packet_buffer_size = 64, 10 | .subtitle_packet_buffer_size = 64, 11 | .video_frame_buffer_size = 2, 12 | .audio_frame_buffer_size = 64, 13 | .subtitle_frame_buffer_size = 64, 14 | .audio_early_threshold = 30, 15 | .audio_late_threshold = 50, 16 | .video_early_threshold = 5, 17 | .video_late_threshold = 50, 18 | .libass_handle = NULL, 19 | .ass_so_handle = NULL, 20 | }; 21 | 22 | Kit_LibraryState *Kit_GetLibraryState() { 23 | return &_library_state; 24 | } 25 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | * Master branch is for the development of v2.0.0 series. 6 | * v1 can be found in the release/v1 branch. v1 will no longer receive features, 7 | but will receive bugfixes. 8 | * v0 can be found in the release/v0 branch. v0 is no longer in active 9 | development and only fixes for severe security bugs are accepted. 10 | 11 | | Version | Supported | 12 | |---------| ------------------ | 13 | | 2.0.x | :white_check_mark: | 14 | | 1.0.x | :white_check_mark: | 15 | | 0.0.x | :x: | 16 | 17 | ## Reporting a Vulnerability 18 | 19 | To report a vulnerability, please email me at katajakasa@gmail.com. Note that it may 20 | take me a few days to respond, as I don't constantly check my emails :) 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: katajakasa 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **Observed behaviour** 17 | A clear and concise description of what actually happened. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Platform (please fill out as much as possible):** 23 | - OS: [e.g. Ubuntu linux 18.04 LTS] 24 | - SDL version 25 | - FFMPEG version 26 | - SDL_Kitchensink version or commit id 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /src/kiterror.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "kitchensink2/kitchensink.h" 7 | 8 | #define KIT_ERRBUFSIZE 1024 9 | 10 | static char _error_available = false; 11 | static char _error_message[KIT_ERRBUFSIZE] = "\0"; 12 | 13 | const char *Kit_GetError() { 14 | if(_error_available) { 15 | _error_available = false; 16 | return _error_message; 17 | } 18 | return NULL; 19 | } 20 | 21 | void Kit_SetError(const char *fmt, ...) { 22 | assert(fmt != NULL); 23 | va_list args; 24 | va_start(args, fmt); 25 | vsnprintf(_error_message, KIT_ERRBUFSIZE, fmt, args); 26 | va_end(args); 27 | _error_available = true; 28 | } 29 | 30 | void Kit_ClearError() { 31 | _error_message[0] = 0; 32 | _error_available = false; 33 | } 34 | -------------------------------------------------------------------------------- /include/kitchensink2/kitcodec.h: -------------------------------------------------------------------------------- 1 | #ifndef KITCODEC_H 2 | #define KITCODEC_H 3 | 4 | /** 5 | * @brief Codec type 6 | * 7 | * @file kitcodec.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #define KIT_CODEC_NAME_MAX 8 17 | #define KIT_CODEC_DESC_MAX 48 18 | 19 | /** 20 | * @brief Contains information about the used codec for playback 21 | */ 22 | typedef struct Kit_Codec { 23 | unsigned int threads; ///< Currently enabled threads (For all decoders) 24 | char name[KIT_CODEC_NAME_MAX]; ///< Codec short name, eg. "ogg" or "webm" 25 | char description[KIT_CODEC_DESC_MAX]; ///< Codec longer, more descriptive name 26 | } Kit_Codec; 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | 32 | #endif // KITCODEC_H 33 | -------------------------------------------------------------------------------- /include/kitchensink2/kitconfig.h: -------------------------------------------------------------------------------- 1 | #ifndef KITCONFIG_H 2 | #define KITCONFIG_H 3 | 4 | /** 5 | * @brief Public API configurations 6 | * 7 | * @file kitconfig.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #if defined _WIN32 || defined __CYGWIN__ 13 | #define KIT_DLL_IMPORT __declspec(dllimport) 14 | #define KIT_DLL_EXPORT __declspec(dllexport) 15 | #define KIT_DLL_LOCAL 16 | #else 17 | #define KIT_DLL_IMPORT __attribute__((visibility("default"))) 18 | #define KIT_DLL_EXPORT __attribute__((visibility("default"))) 19 | #define KIT_DLL_LOCAL __attribute__((visibility("hidden"))) 20 | #endif 21 | 22 | #ifdef KIT_DLL 23 | #ifdef KIT_DLL_EXPORTS 24 | #define KIT_API KIT_DLL_EXPORT 25 | #else 26 | #define KIT_API KIT_DLL_IMPORT 27 | #endif 28 | #define KIT_LOCAL KIT_DLL_LOCAL 29 | #else 30 | #define KIT_API 31 | #define KIT_LOCAL 32 | #endif 33 | 34 | #endif // KITCONFIG_H 35 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kittimer.h: -------------------------------------------------------------------------------- 1 | #ifndef KITTIMER_H 2 | #define KITTIMER_H 3 | 4 | #include "kitchensink2/kitlib.h" 5 | #include 6 | 7 | typedef struct Kit_Timer Kit_Timer; 8 | 9 | KIT_LOCAL Kit_Timer *Kit_CreateTimer(); 10 | KIT_LOCAL Kit_Timer *Kit_CreateSecondaryTimer(const Kit_Timer *src, bool writeable); 11 | 12 | KIT_LOCAL void Kit_InitTimerBase(Kit_Timer *timer); 13 | KIT_LOCAL bool Kit_IsTimerInitialized(const Kit_Timer *timer); 14 | KIT_LOCAL void Kit_ResetTimerBase(Kit_Timer *timer); 15 | KIT_LOCAL void Kit_SetTimerBase(Kit_Timer *timer); 16 | KIT_LOCAL void Kit_AdjustTimerBase(Kit_Timer *timer, double adjust); 17 | KIT_LOCAL void Kit_AddTimerBase(Kit_Timer *timer, double add); 18 | KIT_LOCAL double Kit_GetTimerElapsed(const Kit_Timer *timer); 19 | KIT_LOCAL bool Kit_IsTimerPrimary(const Kit_Timer *timer); 20 | 21 | KIT_LOCAL void Kit_CloseTimer(Kit_Timer **clock); 22 | 23 | #endif // KITTIMER_H 24 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/video/kitvideo.h: -------------------------------------------------------------------------------- 1 | #ifndef KITVIDEO_H 2 | #define KITVIDEO_H 3 | 4 | #include 5 | 6 | #include "kitchensink2/internal/kitdecoder.h" 7 | #include "kitchensink2/internal/kittimer.h" 8 | #include "kitchensink2/kitconfig.h" 9 | #include "kitchensink2/kitsource.h" 10 | 11 | KIT_LOCAL Kit_Decoder *Kit_CreateVideoDecoder( 12 | const Kit_Source *src, const Kit_VideoFormatRequest *format_request, Kit_Timer *sync_timer, int stream_index 13 | ); 14 | KIT_LOCAL int Kit_GetVideoDecoderSDLTexture(Kit_Decoder *dec, SDL_Texture *texture, SDL_Rect *area); 15 | KIT_LOCAL int Kit_LockVideoDecoderRaw(Kit_Decoder *decoder, unsigned char ***data, int **line_size, SDL_Rect *area); 16 | KIT_LOCAL void Kit_UnlockVideoDecoderRaw(Kit_Decoder *decoder); 17 | KIT_LOCAL int Kit_GetVideoDecoderOutputFormat(const Kit_Decoder *dec, Kit_VideoOutputFormat *output); 18 | 19 | #endif // KITVIDEO_H 20 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitlibstate.h: -------------------------------------------------------------------------------- 1 | #ifndef KITLIBSTATE_H 2 | #define KITLIBSTATE_H 3 | 4 | #include "kitchensink2/internal/libass.h" 5 | #include "kitchensink2/kitconfig.h" 6 | 7 | typedef struct Kit_LibraryState { 8 | unsigned int init_flags; 9 | unsigned int thread_count; 10 | unsigned int font_hinting; 11 | unsigned int video_packet_buffer_size; 12 | unsigned int audio_packet_buffer_size; 13 | unsigned int subtitle_packet_buffer_size; 14 | unsigned int video_frame_buffer_size; 15 | unsigned int audio_frame_buffer_size; 16 | unsigned int subtitle_frame_buffer_size; 17 | unsigned int video_early_threshold; 18 | unsigned int video_late_threshold; 19 | unsigned int audio_early_threshold; 20 | unsigned int audio_late_threshold; 21 | ASS_Library *libass_handle; 22 | void *ass_so_handle; 23 | } Kit_LibraryState; 24 | 25 | KIT_LOCAL Kit_LibraryState *Kit_GetLibraryState(); 26 | 27 | #endif // KITLIBSTATE_H 28 | -------------------------------------------------------------------------------- /include/kitchensink2/kiterror.h: -------------------------------------------------------------------------------- 1 | #ifndef KITERROR_H 2 | #define KITERROR_H 3 | 4 | /** 5 | * @brief Error handling functions 6 | * 7 | * @file kiterror.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #include "kitchensink2/kitconfig.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** 19 | * @brief Returns the latest error. This is set by SDL_kitchensink library functions on error. 20 | * 21 | * @return Error message or NULL 22 | */ 23 | KIT_API const char *Kit_GetError(); 24 | 25 | /** 26 | * @brief Sets the error message. This should really only be used by the library. 27 | * 28 | * @param fmt Message format 29 | * @param ... Message arguments 30 | */ 31 | KIT_API void Kit_SetError(const char *fmt, ...); 32 | 33 | /** 34 | * @brief Clears latest error message. After this, Kit_GetError() will return NULL. 35 | */ 36 | KIT_API void Kit_ClearError(); 37 | 38 | #ifdef __cplusplus 39 | } 40 | #endif 41 | 42 | #endif // KITERROR_H 43 | -------------------------------------------------------------------------------- /cmake/Findass.cmake: -------------------------------------------------------------------------------- 1 | # A Simple libass Finder. 2 | # (c) Tuomas Virtanen 2016 (Licensed under MIT license) 3 | # Usage: 4 | # find_package(ass) 5 | # 6 | # Declares: 7 | # * ASS_FOUND 8 | # * ASS_INCLUDE_DIRS 9 | # * ASS_LIBRARIES 10 | # 11 | 12 | set(ASS_SEARCH_PATHS 13 | /usr/local/ 14 | /usr/ 15 | /opt 16 | ) 17 | 18 | find_path(ASS_INCLUDE_DIR ass/ass.h 19 | HINTS 20 | PATH_SUFFIXES include 21 | PATHS ${ASS_SEARCH_PATHS} 22 | ) 23 | 24 | find_library(ASS_LIBRARY 25 | NAMES ass 26 | HINTS 27 | PATH_SUFFIXES lib 28 | PATHS ${ASS_SEARCH_PATHS} 29 | ) 30 | 31 | if(ASS_INCLUDE_DIR AND ASS_LIBRARY) 32 | set(ASS_FOUND TRUE) 33 | endif() 34 | 35 | if(ASS_FOUND) 36 | set(ASS_LIBRARIES ${ASS_LIBRARY}) 37 | set(ASS_INCLUDE_DIRS ${ASS_INCLUDE_DIR}) 38 | message(STATUS "Found libass: ${ASS_LIBRARIES}") 39 | else() 40 | message(WARNING "Could not find libass") 41 | endif() 42 | 43 | mark_as_advanced(ASS_LIBRARY ASS_INCLUDE_DIR ASS_SEARCH_PATHS) 44 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitdecoderthread.h: -------------------------------------------------------------------------------- 1 | #ifndef KITDECODERTHREAD_H 2 | #define KITDECODERTHREAD_H 3 | 4 | #include "kitchensink2/internal/kitdecoder.h" 5 | #include "kitchensink2/internal/kitpacketbuffer.h" 6 | #include "kitchensink2/kitconfig.h" 7 | #include 8 | #include 9 | 10 | typedef struct Kit_DecoderThread { 11 | Kit_PacketBuffer *input; 12 | Kit_Decoder *decoder; 13 | SDL_Thread *thread; 14 | AVPacket *scratch_packet; 15 | SDL_atomic_t run; 16 | } Kit_DecoderThread; 17 | 18 | KIT_LOCAL Kit_DecoderThread *Kit_CreateDecoderThread(Kit_PacketBuffer *input, Kit_Decoder *decoder); 19 | KIT_LOCAL void Kit_StartDecoderThread(Kit_DecoderThread *decoder_thread, const char *name); 20 | KIT_LOCAL void Kit_StopDecoderThread(Kit_DecoderThread *decoder_thread); 21 | KIT_LOCAL void Kit_WaitDecoderThread(Kit_DecoderThread *decoder_thread); 22 | KIT_LOCAL void Kit_CloseDecoderThread(Kit_DecoderThread **ref); 23 | KIT_LOCAL bool Kit_IsDecoderThreadAlive(Kit_DecoderThread *decoder_thread); 24 | 25 | #endif // KITDECODERTHREAD_H 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Tuomas Virtanen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/kitsubtitlepacket.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSUBTITLEPACKET_H 2 | #define KITSUBTITLEPACKET_H 3 | 4 | #include 5 | #include 6 | 7 | #include "kitchensink2/kitconfig.h" 8 | 9 | typedef struct Kit_SubtitlePacket { 10 | double pts_start; 11 | double pts_end; 12 | int x; 13 | int y; 14 | bool clear; 15 | SDL_Surface *surface; 16 | } Kit_SubtitlePacket; 17 | 18 | KIT_LOCAL Kit_SubtitlePacket *Kit_CreateSubtitlePacket(); 19 | KIT_LOCAL void Kit_FreeSubtitlePacket(Kit_SubtitlePacket **packet); 20 | KIT_LOCAL void Kit_SetSubtitlePacketData( 21 | Kit_SubtitlePacket *packet, 22 | bool clear, 23 | double pts_start, 24 | double pts_end, 25 | int pos_x, 26 | int pos_y, 27 | SDL_Surface *surface 28 | ); 29 | KIT_LOCAL void Kit_MoveSubtitlePacketRefs(Kit_SubtitlePacket *dst, Kit_SubtitlePacket *src); 30 | KIT_LOCAL void Kit_DelSubtitlePacketRefs(Kit_SubtitlePacket *packet, bool free_surface); 31 | 32 | // Not implemented 33 | KIT_LOCAL void Kit_CreateSubtitlePacketRef(Kit_SubtitlePacket *dst, Kit_SubtitlePacket *src); 34 | 35 | #endif // KITSUBTITLEPACKET_H 36 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/kitsubtitle.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSUBTITLE_H 2 | #define KITSUBTITLE_H 3 | 4 | #include 5 | 6 | #include "kitchensink2/internal/kitdecoder.h" 7 | #include "kitchensink2/internal/kittimer.h" 8 | #include "kitchensink2/kitconfig.h" 9 | #include "kitchensink2/kitsource.h" 10 | 11 | KIT_LOCAL Kit_Decoder *Kit_CreateSubtitleDecoder( 12 | const Kit_Source *src, 13 | Kit_Timer *sync_timer, 14 | int stream_index, 15 | int video_w, 16 | int video_h, 17 | int screen_w, 18 | int screen_h 19 | ); 20 | KIT_LOCAL void Kit_GetSubtitleDecoderSDLTexture(const Kit_Decoder *dec, SDL_Texture *texture, double sync_ts); 21 | KIT_LOCAL void Kit_SetSubtitleDecoderSize(const Kit_Decoder *dec, int w, int h); 22 | KIT_LOCAL int 23 | Kit_GetSubtitleDecoderSDLTextureInfo(const Kit_Decoder *dec, SDL_Rect *sources, SDL_Rect *targets, int limit); 24 | KIT_LOCAL int Kit_GetSubtitleDecoderOutputFormat(const Kit_Decoder *dec, Kit_SubtitleOutputFormat *output); 25 | KIT_LOCAL int Kit_GetSubtitleDecoderRawFrames( 26 | const Kit_Decoder *dec, unsigned char ***items, SDL_Rect **sources, SDL_Rect **targets, double sync_ts 27 | ); 28 | 29 | #endif // KITSUBTITLE_H 30 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | repository_dispatch: 10 | types: [ run_build ] 11 | 12 | jobs: 13 | ci: 14 | name: Run CI on ${{ matrix.config.name }} 15 | runs-on: ubuntu-24.04 16 | env: 17 | CC: ${{ matrix.config.cc }} 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | config: 23 | - { name: "gcc 12", cc: gcc-12 } 24 | - { name: "gcc 13", cc: gcc-13 } 25 | - { name: "gcc 14", cc: gcc-14 } 26 | - { name: "clang 16", cc: clang-16 } 27 | - { name: "clang 17", cc: clang-17 } 28 | - { name: "clang 18", cc: clang-18 } 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Install Dependencies 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get -y install libsdl2-dev libavcodec-dev libavformat-dev libavutil-dev \ 37 | libswresample-dev libswscale-dev libass-dev clang-tidy ${{ matrix.config.cc }} 38 | 39 | - name: Build 40 | run: | 41 | mkdir build && cd build 42 | cmake -DCMAKE_BUILD_TYPE=Release -DUSE_TIDY=1 -DBUILD_EXAMPLES=1 .. 43 | make -j2 44 | -------------------------------------------------------------------------------- /src/internal/utils/kithelpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "kitchensink2/internal/utils/kithelpers.h" 5 | 6 | static const char *const font_mime[] = { 7 | "application/x-font-ttf", 8 | "application/x-font-truetype", 9 | "application/x-truetype-font", 10 | "application/x-font-opentype", 11 | "application/vnd.ms-opentype", 12 | "application/font-sfnt", 13 | NULL 14 | }; 15 | 16 | double Kit_GetSystemTime() { 17 | return (double)av_gettime() / 1000000.0; 18 | } 19 | 20 | bool Kit_StreamIsFontAttachment(const AVStream *stream) { 21 | if(stream->codecpar->codec_type != AVMEDIA_TYPE_ATTACHMENT) 22 | return false; 23 | const AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE); 24 | if(tag) { 25 | for(int n = 0; font_mime[n]; n++) { 26 | if(av_strcasecmp(font_mime[n], tag->value) == 0) { 27 | return true; 28 | } 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | int Kit_max(int a, int b) { 35 | return a > b ? a : b; 36 | } 37 | 38 | int Kit_min(int a, int b) { 39 | return a < b ? a : b; 40 | } 41 | 42 | int Kit_clamp(int v, int min, int max) { 43 | return Kit_max(Kit_min(v, max), min); 44 | } 45 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitdemuxerthread.h: -------------------------------------------------------------------------------- 1 | #ifndef KITDEMUXERTHREAD_H 2 | #define KITDEMUXERTHREAD_H 3 | 4 | #include "kitchensink2/internal/kitbufferindex.h" 5 | #include "kitchensink2/internal/kitdemuxer.h" 6 | #include "kitchensink2/internal/kitpacketbuffer.h" 7 | #include "kitchensink2/kitconfig.h" 8 | #include "kitchensink2/kitsource.h" 9 | #include 10 | #include 11 | 12 | typedef struct Kit_DemuxerThread { 13 | Kit_Demuxer *demuxer; 14 | SDL_Thread *thread; 15 | SDL_atomic_t run; 16 | SDL_atomic_t seek; 17 | int64_t seek_target; 18 | } Kit_DemuxerThread; 19 | 20 | KIT_LOCAL Kit_DemuxerThread *Kit_CreateDemuxerThread(Kit_Demuxer *demuxer); 21 | KIT_LOCAL void Kit_CloseDemuxerThread(Kit_DemuxerThread **demuxer); 22 | 23 | KIT_LOCAL void Kit_SeekDemuxerThread(Kit_DemuxerThread *demuxer_thread, int64_t seek_target); 24 | KIT_LOCAL Kit_PacketBuffer * 25 | Kit_GetDemuxerThreadPacketBuffer(const Kit_DemuxerThread *demuxer_thread, Kit_BufferIndex buffer_index); 26 | 27 | KIT_LOCAL void Kit_StartDemuxerThread(Kit_DemuxerThread *demuxer_thread); 28 | KIT_LOCAL void Kit_StopDemuxerThread(Kit_DemuxerThread *demuxer_thread); 29 | KIT_LOCAL void Kit_WaitDemuxerThread(Kit_DemuxerThread *demuxer_thread); 30 | KIT_LOCAL bool Kit_IsDemuxerThreadAlive(Kit_DemuxerThread *demuxer_thread); 31 | 32 | #endif // KITDEMUXERTHREAD_H 33 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitdemuxer.h: -------------------------------------------------------------------------------- 1 | #ifndef KITDEMUXER_H 2 | #define KITDEMUXER_H 3 | 4 | #include "kitchensink2/internal/kitbufferindex.h" 5 | #include "kitchensink2/internal/kitpacketbuffer.h" 6 | #include "kitchensink2/kitconfig.h" 7 | #include "kitchensink2/kitsource.h" 8 | #include 9 | #include 10 | 11 | typedef struct Kit_Demuxer { 12 | const Kit_Source *src; 13 | Kit_PacketBuffer *buffers[KIT_INDEX_COUNT]; 14 | int stream_indexes[KIT_INDEX_COUNT]; 15 | AVPacket *scratch_packet; 16 | } Kit_Demuxer; 17 | 18 | KIT_LOCAL Kit_Demuxer *Kit_CreateDemuxer(const Kit_Source *src, int video_index, int audio_index, int subtitle_index); 19 | KIT_LOCAL void Kit_CloseDemuxer(Kit_Demuxer **demuxer); 20 | KIT_LOCAL bool Kit_RunDemuxer(Kit_Demuxer *demuxer); 21 | KIT_LOCAL Kit_PacketBuffer *Kit_GetDemuxerPacketBuffer(const Kit_Demuxer *demuxer, Kit_BufferIndex buffer_index); 22 | KIT_LOCAL void Kit_ClearDemuxerBuffers(const Kit_Demuxer *demuxer); 23 | KIT_LOCAL void Kit_SignalDemuxer(const Kit_Demuxer *demuxer); 24 | KIT_LOCAL bool Kit_DemuxerSeek(Kit_Demuxer *demuxer, int64_t seek_target); 25 | KIT_LOCAL void Kit_SetDemuxerStreamIndex(Kit_Demuxer *demuxer, Kit_BufferIndex index, int stream_index); 26 | KIT_LOCAL void Kit_GetDemuxerBufferState( 27 | const Kit_Demuxer *demuxer, Kit_BufferIndex buffer_index, unsigned int *length, unsigned int *capacity 28 | ); 29 | 30 | #endif // KITDEMUXER_H 31 | -------------------------------------------------------------------------------- /include/kitchensink2/kitutils.h: -------------------------------------------------------------------------------- 1 | #ifndef KITUTILS_H 2 | #define KITUTILS_H 3 | 4 | /** 5 | * @brief Helpful utilities 6 | * 7 | * @file kitutils.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #include "kitchensink2/kitconfig.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** 19 | * @brief Returns a descriptive string for SDL audio format types 20 | * 21 | * @param type SDL_AudioFormat 22 | * @return Format string, eg. "AUDIO_S8". 23 | */ 24 | KIT_API const char *Kit_GetSDLAudioFormatString(unsigned int type); 25 | 26 | /** 27 | * @brief Returns a descriptive string for SDL pixel format types 28 | * 29 | * @param type SDL_PixelFormat 30 | * @return Format string, eg. "SDL_PIXELFORMAT_YV12" 31 | */ 32 | KIT_API const char *Kit_GetSDLPixelFormatString(unsigned int type); 33 | 34 | /** 35 | * @brief Returns a descriptive string for Kitchensink stream types 36 | * 37 | * @param type Kit_StreamType 38 | * @return Format string, eg. "KIT_STREAMTYPE_VIDEO" 39 | */ 40 | KIT_API const char *Kit_GetKitStreamTypeString(unsigned int type); 41 | 42 | /** 43 | * @brief Returns a descriptive string for Kitchensink hardware video decoder type 44 | * @param type Kit_HardwareDeviceType 45 | * @return Format string, eg. "KIT_HWDEVICE_TYPE_VDPAU" 46 | */ 47 | KIT_API const char *Kit_GetHardwareDecoderTypeString(unsigned int type); 48 | 49 | #ifdef __cplusplus 50 | } 51 | #endif 52 | 53 | #endif // KITUTILS_H 54 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/kitatlas.h: -------------------------------------------------------------------------------- 1 | #ifndef KITATLAS_H 2 | #define KITATLAS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "kitchensink2/kitconfig.h" 9 | 10 | typedef struct Kit_TextureAtlasItem { 11 | SDL_Rect source; //< Source coordinates on cache surface 12 | SDL_Rect target; //< Target coordinates on output surface 13 | } Kit_TextureAtlasItem; 14 | 15 | typedef struct Kit_Shelf { 16 | uint16_t width; 17 | uint16_t height; 18 | uint16_t count; 19 | } Kit_Shelf; 20 | 21 | typedef struct Kit_TextureAtlas { 22 | int cur_items; //< Current items count 23 | int max_items; //< Maximum items count 24 | int max_shelves; //< Maximum shelf count 25 | int w; //< Current atlas width 26 | int h; //< Current atlas height 27 | Kit_TextureAtlasItem *items; //< Cached items 28 | Kit_Shelf *shelves; //< Atlas shelves 29 | } Kit_TextureAtlas; 30 | 31 | KIT_LOCAL Kit_TextureAtlas *Kit_CreateAtlas(); 32 | KIT_LOCAL void Kit_FreeAtlas(Kit_TextureAtlas *atlas); 33 | KIT_LOCAL void Kit_ClearAtlasContent(Kit_TextureAtlas *atlas); 34 | KIT_LOCAL void Kit_CheckAtlasTextureSize(Kit_TextureAtlas *atlas, SDL_Texture *texture); 35 | KIT_LOCAL int Kit_GetAtlasItems(const Kit_TextureAtlas *atlas, SDL_Rect *sources, SDL_Rect *targets, int limit); 36 | KIT_LOCAL int 37 | Kit_AddAtlasItem(Kit_TextureAtlas *atlas, SDL_Texture *texture, const SDL_Surface *surface, const SDL_Rect *target); 38 | 39 | #endif // KITATLAS_H 40 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitpacketbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef KITFRAMESTREAM_H 2 | #define KITFRAMESTREAM_H 3 | 4 | #include "kitchensink2/kitconfig.h" 5 | #include 6 | 7 | typedef void *(*buf_obj_alloc)(); 8 | typedef void (*buf_obj_unref)(void *obj); 9 | typedef void (*buf_obj_free)(void **obj); 10 | typedef void (*buf_obj_move)(void *dst, void *src); 11 | typedef void (*buf_obj_ref)(void *dst, void *src); 12 | 13 | typedef struct Kit_PacketBuffer Kit_PacketBuffer; 14 | 15 | KIT_LOCAL Kit_PacketBuffer *Kit_CreatePacketBuffer( 16 | size_t capacity, 17 | buf_obj_alloc alloc_cb, 18 | buf_obj_unref unref_cb, 19 | buf_obj_free free_cb, 20 | buf_obj_move move_cb, 21 | buf_obj_ref ref_cb 22 | ); 23 | KIT_LOCAL void Kit_FreePacketBuffer(Kit_PacketBuffer **buffer); 24 | 25 | KIT_LOCAL bool Kit_IsPacketBufferFull(const Kit_PacketBuffer *buffer); 26 | KIT_LOCAL bool Kit_IsPacketBufferEmpty(const Kit_PacketBuffer *buffer); 27 | KIT_LOCAL size_t Kit_GetPacketBufferCapacity(const Kit_PacketBuffer *buffer); 28 | KIT_LOCAL size_t Kit_GetPacketBufferLength(const Kit_PacketBuffer *buffer); 29 | 30 | KIT_LOCAL void Kit_SignalPacketBuffer(Kit_PacketBuffer *buffer); 31 | KIT_LOCAL void Kit_FlushPacketBuffer(Kit_PacketBuffer *buffer); 32 | KIT_LOCAL bool Kit_WritePacketBuffer(Kit_PacketBuffer *buffer, void *src); 33 | KIT_LOCAL bool Kit_ReadPacketBuffer(Kit_PacketBuffer *buffer, void *dst, int timeout); 34 | 35 | KIT_LOCAL bool Kit_BeginPacketBufferRead(Kit_PacketBuffer *buffer, void *dst, int timeout); 36 | KIT_LOCAL void Kit_FinishPacketBufferRead(Kit_PacketBuffer *buffer); 37 | KIT_LOCAL void Kit_CancelPacketBufferRead(Kit_PacketBuffer *buffer); 38 | 39 | #endif // KITFRAMESTREAM_H 40 | -------------------------------------------------------------------------------- /src/internal/subtitle/kitsubtitlepacket.c: -------------------------------------------------------------------------------- 1 | #include "kitchensink2/internal/subtitle/kitsubtitlepacket.h" 2 | 3 | Kit_SubtitlePacket *Kit_CreateSubtitlePacket() { 4 | return calloc(1, sizeof(Kit_SubtitlePacket)); 5 | } 6 | 7 | void Kit_FreeSubtitlePacket(Kit_SubtitlePacket **ref) { 8 | if(!ref || !*ref) 9 | return; 10 | Kit_SubtitlePacket *packet = *ref; 11 | SDL_FreeSurface(packet->surface); 12 | free(packet); 13 | *ref = NULL; 14 | } 15 | 16 | void Kit_SetSubtitlePacketData( 17 | Kit_SubtitlePacket *packet, 18 | bool clear, 19 | double pts_start, 20 | double pts_end, 21 | int pos_x, 22 | int pos_y, 23 | SDL_Surface *surface 24 | ) { 25 | if(packet->surface) 26 | SDL_FreeSurface(packet->surface); 27 | packet->pts_start = pts_start; 28 | packet->pts_end = pts_end; 29 | packet->x = pos_x; 30 | packet->y = pos_y; 31 | packet->surface = surface; 32 | packet->clear = clear; 33 | } 34 | 35 | void Kit_MoveSubtitlePacketRefs(Kit_SubtitlePacket *dst, Kit_SubtitlePacket *src) { 36 | if(dst->surface) 37 | SDL_FreeSurface(dst->surface); 38 | dst->pts_start = src->pts_start; 39 | dst->pts_end = src->pts_end; 40 | dst->x = src->x; 41 | dst->y = src->y; 42 | dst->surface = src->surface; 43 | dst->clear = src->clear; 44 | memset(src, 0, sizeof(Kit_SubtitlePacket)); 45 | } 46 | 47 | void Kit_CreateSubtitlePacketRef(Kit_SubtitlePacket *dst, Kit_SubtitlePacket *src) { 48 | } 49 | 50 | void Kit_DelSubtitlePacketRefs(Kit_SubtitlePacket *packet, bool free_surface) { 51 | if(packet->surface && free_surface) 52 | SDL_FreeSurface(packet->surface); 53 | memset(packet, 0, sizeof(Kit_SubtitlePacket)); 54 | } 55 | -------------------------------------------------------------------------------- /src/internal/audio/kitaudioutils.c: -------------------------------------------------------------------------------- 1 | #include "kitchensink2/internal/audio/kitaudioutils.h" 2 | 3 | #include 4 | 5 | enum AVSampleFormat Kit_FindAVSampleFormat(int format) 6 | { 7 | switch(format) { 8 | case AUDIO_U8: 9 | return AV_SAMPLE_FMT_U8; 10 | case AUDIO_S16SYS: 11 | return AV_SAMPLE_FMT_S16; 12 | case AUDIO_S32SYS: 13 | return AV_SAMPLE_FMT_S32; 14 | default: 15 | return AV_SAMPLE_FMT_NONE; 16 | } 17 | } 18 | 19 | void Kit_FindAVChannelLayout(int channels, AVChannelLayout *layout) { 20 | switch(channels) { 21 | case 1: 22 | *layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO; 23 | break; 24 | case 2: 25 | *layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; 26 | break; 27 | default: 28 | *layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO; 29 | break; 30 | } 31 | } 32 | 33 | int Kit_FindChannelLayout(const AVChannelLayout *channel_layout) { 34 | if(channel_layout->nb_channels > 2) 35 | return 2; 36 | return channel_layout->nb_channels; 37 | } 38 | 39 | int Kit_FindBytes(enum AVSampleFormat fmt) { 40 | switch(fmt) { 41 | case AV_SAMPLE_FMT_U8P: 42 | case AV_SAMPLE_FMT_U8: 43 | return 1; 44 | case AV_SAMPLE_FMT_S32P: 45 | case AV_SAMPLE_FMT_S32: 46 | return 4; 47 | default: 48 | return 2; 49 | } 50 | } 51 | 52 | int Kit_FindSDLSampleFormat(enum AVSampleFormat fmt) { 53 | switch(fmt) { 54 | case AV_SAMPLE_FMT_U8P: 55 | case AV_SAMPLE_FMT_U8: 56 | return AUDIO_U8; 57 | case AV_SAMPLE_FMT_S32P: 58 | case AV_SAMPLE_FMT_S32: 59 | return AUDIO_S32SYS; 60 | default: 61 | return AUDIO_S16SYS; 62 | } 63 | } 64 | 65 | int Kit_FindSignedness(enum AVSampleFormat fmt) { 66 | switch(fmt) { 67 | case AV_SAMPLE_FMT_U8P: 68 | case AV_SAMPLE_FMT_U8: 69 | return 0; 70 | default: 71 | return 1; 72 | } 73 | } -------------------------------------------------------------------------------- /cmake/Findffmpeg.cmake: -------------------------------------------------------------------------------- 1 | # A Simple FFMPEG Finder. 2 | # (c) Tuomas Virtanen 2016 (Licensed under MIT license) 3 | # Usage: 4 | # find_package(ffmpeg COMPONENTS avcodec avutil ...) 5 | # 6 | # Declares: 7 | # * FFMPEG_FOUND 8 | # * FFMPEG_INCLUDE_DIRS 9 | # * FFMPEG_LIBRARIES 10 | # 11 | # Also declares ${component}_FOUND for each component, eg. avcodec_FOUND etc. 12 | # 13 | 14 | set(FFMPEG_SEARCH_PATHS 15 | /usr/local 16 | /usr 17 | /opt 18 | ) 19 | 20 | set(FFMPEG_COMPONENTS 21 | avcodec 22 | avformat 23 | avdevice 24 | avfilter 25 | avutil 26 | swresample 27 | swscale 28 | ) 29 | 30 | set(FFMPEG_INCLUDE_DIRS) 31 | set(FFMPEG_LIBRARIES) 32 | set(FFMPEG_FOUND TRUE) 33 | 34 | # Walk through all components declared above, and try to find the ones that have been asked 35 | foreach(comp ${FFMPEG_COMPONENTS}) 36 | list(FIND ffmpeg_FIND_COMPONENTS ${comp} _index) 37 | if(${_index} GREATER -1) 38 | # Component requested, try to look up the library and header for it. 39 | find_path(${comp}_INCLUDE_DIR lib${comp}/${comp}.h 40 | HINTS 41 | PATH_SUFFIXES include 42 | PATHS ${FFMPEG_SEARCH_PATHS} 43 | ) 44 | find_library(${comp}_LIBRARY ${comp} 45 | HINTS 46 | PATH_SUFFIXES lib 47 | PATHS ${FFMPEG_SEARCH_PATHS} 48 | ) 49 | 50 | # If library and header was found, set proper variables 51 | # Otherwise print out a warning! 52 | if(${comp}_LIBRARY AND ${comp}_INCLUDE_DIR) 53 | set(${comp}_FOUND TRUE) 54 | list(APPEND FFMPEG_INCLUDE_DIRS ${${comp}_INCLUDE_DIR}) 55 | list(APPEND FFMPEG_LIBRARIES ${${comp}_LIBRARY}) 56 | else() 57 | set(FFMPEG_FOUND FALSE) 58 | set(${comp}_FOUND FALSE) 59 | message(WARNING "Could not find component: ${comp}") 60 | endif() 61 | 62 | # Mark the temporary variables as hidden in the ui 63 | mark_as_advanced(${${comp}_LIBRARY} ${${comp}_INCLUDE_DIR}) 64 | endif() 65 | endforeach() 66 | 67 | if(FFMPEG_FOUND) 68 | message(STATUS "Found FFMPEG: ${FFMPEG_LIBRARIES}") 69 | else() 70 | message(WARNING "Could not find FFMPEG") 71 | endif() 72 | 73 | mark_as_advanced(FFMPEG_COMPONENTS FFMPEG_SEARCH_PATHS) 74 | -------------------------------------------------------------------------------- /src/internal/libass.c: -------------------------------------------------------------------------------- 1 | #ifdef USE_DYNAMIC_LIBASS 2 | 3 | #include "kitchensink2/internal/libass.h" 4 | #include 5 | 6 | ASS_Library *(*ass_library_init)(void); 7 | void (*ass_library_done)(ASS_Library *priv); 8 | void (*ass_process_codec_private)(ASS_Track *track, char *data, int size); 9 | void (*ass_set_message_cb)( 10 | ASS_Library *priv, void (*msg_cb)(int level, const char *fmt, va_list args, void *data), void *data 11 | ); 12 | ASS_Renderer *(*ass_renderer_init)(ASS_Library *); 13 | void (*ass_renderer_done)(ASS_Renderer *priv); 14 | void (*ass_set_frame_size)(ASS_Renderer *priv, int w, int h); 15 | void (*ass_set_hinting)(ASS_Renderer *priv, ASS_Hinting ht); 16 | void (*ass_set_fonts)( 17 | ASS_Renderer *priv, const char *default_font, const char *default_family, int dfp, const char *config, int update 18 | ); 19 | ASS_Image *(*ass_render_frame)(ASS_Renderer *priv, ASS_Track *track, long long now, int *detect_change); 20 | ASS_Track *(*ass_new_track)(ASS_Library *); 21 | void (*ass_free_track)(ASS_Track *track); 22 | void (*ass_process_data)(ASS_Track *track, char *data, int size); 23 | void (*ass_process_chunk)(ASS_Track *track, char *data, int size, long long timecode, long long duration); 24 | void (*ass_add_font)(ASS_Library *library, char *name, char *data, int data_size); 25 | void (*ass_set_storage_size)(ASS_Renderer *priv, int w, int h); 26 | 27 | int load_libass(void *handle) { 28 | ass_library_init = SDL_LoadFunction(handle, "ass_library_init"); 29 | ass_library_done = SDL_LoadFunction(handle, "ass_library_done"); 30 | ass_set_message_cb = SDL_LoadFunction(handle, "ass_set_message_cb"); 31 | ass_renderer_init = SDL_LoadFunction(handle, "ass_renderer_init"); 32 | ass_renderer_done = SDL_LoadFunction(handle, "ass_renderer_done"); 33 | ass_set_frame_size = SDL_LoadFunction(handle, "ass_set_frame_size"); 34 | ass_set_hinting = SDL_LoadFunction(handle, "ass_set_hinting"); 35 | ass_set_fonts = SDL_LoadFunction(handle, "ass_set_fonts"); 36 | ass_render_frame = SDL_LoadFunction(handle, "ass_render_frame"); 37 | ass_new_track = SDL_LoadFunction(handle, "ass_new_track"); 38 | ass_free_track = SDL_LoadFunction(handle, "ass_free_track"); 39 | ass_process_data = SDL_LoadFunction(handle, "ass_process_data"); 40 | ass_add_font = SDL_LoadFunction(handle, "ass_add_font"); 41 | ass_process_codec_private = SDL_LoadFunction(handle, "ass_process_codec_private"); 42 | ass_process_chunk = SDL_LoadFunction(handle, "ass_process_chunk"); 43 | ass_set_storage_size = SDL_LoadFunction(handle, "ass_set_storage_size"); 44 | return 0; 45 | } 46 | 47 | #endif // USE_DYNAMIC_LIBASS 48 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/libass.h: -------------------------------------------------------------------------------- 1 | #ifndef KITLIBASS_H 2 | #define KITLIBASS_H 3 | 4 | #ifndef USE_DYNAMIC_LIBASS 5 | 6 | #include 7 | 8 | #else // USE_DYNAMIC_LIBASS 9 | 10 | #include 11 | #include 12 | 13 | #include "kitchensink2/kitconfig.h" 14 | 15 | typedef struct ass_library ASS_Library; 16 | typedef struct ass_renderer ASS_Renderer; 17 | typedef struct ass_track ASS_Track; 18 | 19 | typedef struct ass_image { 20 | int w, h; 21 | int stride; 22 | unsigned char *bitmap; 23 | uint32_t color; 24 | int dst_x, dst_y; 25 | struct ass_image *next; 26 | enum 27 | { 28 | IMAGE_TYPE_CHARACTER, 29 | IMAGE_TYPE_OUTLINE, 30 | IMAGE_TYPE_SHADOW 31 | } type; 32 | } ASS_Image; 33 | 34 | typedef enum 35 | { 36 | ASS_HINTING_NONE = 0, 37 | ASS_HINTING_LIGHT, 38 | ASS_HINTING_NORMAL, 39 | ASS_HINTING_NATIVE 40 | } ASS_Hinting; 41 | 42 | extern KIT_LOCAL ASS_Library *(*ass_library_init)(void); 43 | extern KIT_LOCAL void (*ass_library_done)(ASS_Library *priv); 44 | extern KIT_LOCAL void (*ass_process_codec_private)(ASS_Track *track, char *data, int size); 45 | extern KIT_LOCAL void (*ass_set_message_cb)( 46 | ASS_Library *priv, void (*msg_cb)(int level, const char *fmt, va_list args, void *data), void *data 47 | ); 48 | extern KIT_LOCAL ASS_Renderer *(*ass_renderer_init)(ASS_Library *); 49 | extern KIT_LOCAL void (*ass_renderer_done)(ASS_Renderer *priv); 50 | extern KIT_LOCAL void (*ass_set_frame_size)(ASS_Renderer *priv, int w, int h); 51 | extern KIT_LOCAL void (*ass_set_hinting)(ASS_Renderer *priv, ASS_Hinting ht); 52 | extern KIT_LOCAL void (*ass_set_fonts)( 53 | ASS_Renderer *priv, const char *default_font, const char *default_family, int dfp, const char *config, int update 54 | ); 55 | extern KIT_LOCAL ASS_Image *(*ass_render_frame)( 56 | ASS_Renderer *priv, ASS_Track *track, long long now, int *detect_change 57 | ); 58 | extern KIT_LOCAL ASS_Track *(*ass_new_track)(ASS_Library *); 59 | extern KIT_LOCAL void (*ass_free_track)(ASS_Track *track); 60 | extern KIT_LOCAL void (*ass_process_data)(ASS_Track *track, char *data, int size); 61 | extern KIT_LOCAL void (*ass_process_chunk)( 62 | ASS_Track *track, char *data, int size, long long timecode, long long duration 63 | ); 64 | extern KIT_LOCAL void (*ass_add_font)(ASS_Library *library, char *name, char *data, int data_size); 65 | extern KIT_LOCAL void (*ass_set_storage_size)(ASS_Renderer *priv, int w, int h); 66 | 67 | KIT_LOCAL int load_libass(void *handle); 68 | 69 | #endif // USE_DYNAMIC_LIBASS 70 | 71 | // For compatibility 72 | #ifndef ASS_FONTPROVIDER_AUTODETECT 73 | #define ASS_FONTPROVIDER_AUTODETECT 1 74 | #endif 75 | 76 | #endif // KITLIBASS_H 77 | -------------------------------------------------------------------------------- /src/internal/kittimer.c: -------------------------------------------------------------------------------- 1 | #include "kitchensink2/internal/kittimer.h" 2 | #include "kitchensink2/internal/utils/kithelpers.h" 3 | #include 4 | 5 | typedef struct Kit_TimerValue { 6 | int count; 7 | bool initialized; 8 | double value; 9 | } Kit_TimerValue; 10 | 11 | struct Kit_Timer { 12 | bool writeable; 13 | Kit_TimerValue *ref; 14 | }; 15 | 16 | Kit_Timer *Kit_CreateTimer() { 17 | Kit_Timer *timer; 18 | Kit_TimerValue *value; 19 | 20 | if((timer = calloc(1, sizeof(Kit_Timer))) == NULL) { 21 | goto exit_0; 22 | } 23 | if((value = calloc(1, sizeof(Kit_TimerValue))) == NULL) { 24 | goto exit_1; 25 | } 26 | 27 | value->count = 1; 28 | value->value = 0; 29 | value->initialized = false; 30 | timer->ref = value; 31 | timer->writeable = true; 32 | return timer; 33 | 34 | exit_1: 35 | free(timer); 36 | exit_0: 37 | return NULL; 38 | } 39 | 40 | Kit_Timer *Kit_CreateSecondaryTimer(const Kit_Timer *src, bool writeable) { 41 | Kit_Timer *timer; 42 | if((timer = calloc(1, sizeof(Kit_Timer))) == NULL) { 43 | return NULL; 44 | } 45 | timer->ref = src->ref; 46 | timer->ref->count++; 47 | timer->writeable = writeable; 48 | return timer; 49 | } 50 | 51 | void Kit_InitTimerBase(Kit_Timer *timer) { 52 | if(timer->writeable && !timer->ref->initialized) { 53 | timer->ref->value = Kit_GetSystemTime(); 54 | timer->ref->initialized = true; 55 | } 56 | } 57 | 58 | bool Kit_IsTimerInitialized(const Kit_Timer *timer) { 59 | return timer->ref->initialized; 60 | } 61 | 62 | void Kit_ResetTimerBase(Kit_Timer *timer) { 63 | if(timer->writeable) { 64 | timer->ref->initialized = false; 65 | } 66 | } 67 | 68 | void Kit_SetTimerBase(Kit_Timer *timer) { 69 | if(timer->writeable) { 70 | timer->ref->value = Kit_GetSystemTime(); 71 | timer->ref->initialized = true; 72 | } 73 | } 74 | 75 | void Kit_AdjustTimerBase(Kit_Timer *timer, double adjust) { 76 | if(timer->writeable) { 77 | timer->ref->value = Kit_GetSystemTime() - adjust; 78 | timer->ref->initialized = true; 79 | } 80 | } 81 | 82 | void Kit_AddTimerBase(Kit_Timer *timer, double add) { 83 | if(timer->writeable) { 84 | timer->ref->value += add; 85 | timer->ref->initialized = true; 86 | } 87 | } 88 | 89 | double Kit_GetTimerElapsed(const Kit_Timer *timer) { 90 | return Kit_GetSystemTime() - timer->ref->value; 91 | } 92 | 93 | bool Kit_IsTimerPrimary(const Kit_Timer *timer) { 94 | return timer->writeable; 95 | } 96 | 97 | void Kit_CloseTimer(Kit_Timer **ref) { 98 | if(!ref || !*ref) 99 | return; 100 | Kit_Timer *timer = *ref; 101 | if(--timer->ref->count == 0) { 102 | free(timer->ref); 103 | } 104 | free(timer); 105 | *ref = NULL; 106 | } 107 | -------------------------------------------------------------------------------- /src/internal/kitdemuxerthread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "kitchensink2/internal/kitdemuxerthread.h" 5 | #include "kitchensink2/kiterror.h" 6 | 7 | static int Kit_DemuxMain(void *ptr) { 8 | Kit_DemuxerThread *thread = ptr; 9 | while(SDL_AtomicGet(&thread->run)) { 10 | if(SDL_AtomicGet(&thread->seek)) { 11 | Kit_DemuxerSeek(thread->demuxer, thread->seek_target); 12 | SDL_AtomicSet(&thread->seek, 0); 13 | } 14 | if(!Kit_RunDemuxer(thread->demuxer)) 15 | break; 16 | } 17 | SDL_AtomicSet(&thread->run, 0); 18 | return 0; 19 | } 20 | 21 | Kit_DemuxerThread *Kit_CreateDemuxerThread(Kit_Demuxer *demuxer) { 22 | Kit_DemuxerThread *demuxer_thread = NULL; 23 | 24 | if((demuxer_thread = calloc(1, sizeof(Kit_DemuxerThread))) == NULL) { 25 | Kit_SetError("Unable to allocate demuxer thread"); 26 | goto exit_0; 27 | } 28 | 29 | demuxer_thread->thread = NULL; 30 | demuxer_thread->demuxer = demuxer; 31 | demuxer_thread->seek_target = 0; 32 | SDL_AtomicSet(&demuxer_thread->run, 0); 33 | SDL_AtomicSet(&demuxer_thread->seek, 0); 34 | return demuxer_thread; 35 | 36 | exit_0: 37 | return false; 38 | } 39 | 40 | void Kit_SeekDemuxerThread(Kit_DemuxerThread *demuxer_thread, int64_t seek_target) { 41 | if(SDL_AtomicGet(&demuxer_thread->seek)) 42 | return; 43 | demuxer_thread->seek_target = seek_target; 44 | SDL_AtomicSet(&demuxer_thread->seek, 1); 45 | } 46 | 47 | void Kit_StartDemuxerThread(Kit_DemuxerThread *demuxer_thread) { 48 | if(!demuxer_thread || demuxer_thread->thread) 49 | return; 50 | SDL_AtomicSet(&demuxer_thread->run, 1); 51 | demuxer_thread->thread = SDL_CreateThread(Kit_DemuxMain, "SDL_Kitchensink demuxer thread", demuxer_thread); 52 | } 53 | 54 | void Kit_StopDemuxerThread(Kit_DemuxerThread *demuxer_thread) { 55 | if(!demuxer_thread || !demuxer_thread->thread) 56 | return; 57 | SDL_AtomicSet(&demuxer_thread->run, 0); 58 | } 59 | 60 | void Kit_WaitDemuxerThread(Kit_DemuxerThread *demuxer_thread) { 61 | if(!demuxer_thread || !demuxer_thread->thread) 62 | return; 63 | SDL_WaitThread(demuxer_thread->thread, NULL); 64 | demuxer_thread->thread = NULL; 65 | } 66 | 67 | Kit_PacketBuffer * 68 | Kit_GetDemuxerThreadPacketBuffer(const Kit_DemuxerThread *demuxer_thread, Kit_BufferIndex buffer_index) { 69 | assert(demuxer_thread); 70 | return Kit_GetDemuxerPacketBuffer(demuxer_thread->demuxer, buffer_index); 71 | } 72 | 73 | bool Kit_IsDemuxerThreadAlive(Kit_DemuxerThread *demuxer_thread) { 74 | return SDL_AtomicGet(&demuxer_thread->run); 75 | } 76 | 77 | void Kit_CloseDemuxerThread(Kit_DemuxerThread **ref) { 78 | if(!ref || !*ref) 79 | return; 80 | 81 | Kit_DemuxerThread *demuxer_thread = *ref; 82 | Kit_StopDemuxerThread(demuxer_thread); 83 | Kit_WaitDemuxerThread(demuxer_thread); 84 | free(demuxer_thread); 85 | *ref = NULL; 86 | } 87 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/subtitle/renderers/kitsubrenderer.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSUBRENDERER_H 2 | #define KITSUBRENDERER_H 3 | 4 | #include 5 | 6 | #include "kitchensink2/internal/kitdecoder.h" 7 | #include "kitchensink2/internal/subtitle/kitatlas.h" 8 | 9 | typedef struct Kit_SubtitleRenderer Kit_SubtitleRenderer; 10 | 11 | typedef void (*renderer_render_cb)(Kit_SubtitleRenderer *ren, void *src, double pts, double start, double end); 12 | typedef int (*renderer_get_data_cb)( 13 | Kit_SubtitleRenderer *ren, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 14 | ); 15 | typedef int (*renderer_get_raw_frames_cb)( 16 | Kit_SubtitleRenderer *renderer, unsigned char ***frames, SDL_Rect **sources, SDL_Rect **targets, double current_pts 17 | ); 18 | typedef void (*renderer_set_size_cb)(Kit_SubtitleRenderer *ren, int w, int h); 19 | typedef void (*renderer_flush_cb)(Kit_SubtitleRenderer *ren); 20 | typedef void (*renderer_signal_cb)(Kit_SubtitleRenderer *ren); 21 | typedef void (*renderer_close_cb)(Kit_SubtitleRenderer *ren); 22 | 23 | struct Kit_SubtitleRenderer { 24 | Kit_Decoder *decoder; 25 | void *userdata; 26 | renderer_render_cb render_cb; ///< Subtitle rendering function callback 27 | renderer_get_data_cb get_data_cb; ///< Subtitle data getter function callback 28 | renderer_set_size_cb set_size_cb; ///< Screen size setter function callback 29 | renderer_flush_cb flush_cb; ///< Flush subtitle renderer buffers 30 | renderer_signal_cb signal_cb; ///< Shutdown signal handler function callback 31 | renderer_close_cb close_cb; ///< Subtitle renderer close function callback 32 | renderer_get_raw_frames_cb get_raw_frames_cb; ///< Get raw frames function callback 33 | }; 34 | 35 | KIT_LOCAL Kit_SubtitleRenderer *Kit_CreateSubtitleRenderer( 36 | Kit_Decoder *decoder, 37 | renderer_render_cb render_cb, 38 | renderer_get_data_cb get_data_cb, 39 | renderer_get_raw_frames_cb get_raw_frames_cb, 40 | renderer_set_size_cb set_size_cb, 41 | renderer_flush_cb flush_cb, 42 | renderer_signal_cb signal_cb, 43 | renderer_close_cb close_cb, 44 | void *userdata 45 | ); 46 | KIT_LOCAL void 47 | Kit_RunSubtitleRenderer(Kit_SubtitleRenderer *renderer, void *src, double pts, double start, double end); 48 | KIT_LOCAL int Kit_GetSubtitleRendererSDLTexture( 49 | Kit_SubtitleRenderer *renderer, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 50 | ); 51 | KIT_LOCAL int Kit_GetSubtitleRendererRawFrames( 52 | Kit_SubtitleRenderer *renderer, unsigned char ***frames, SDL_Rect **sources, SDL_Rect **targets, double current_pts 53 | ); 54 | KIT_LOCAL void Kit_SetSubtitleRendererSize(Kit_SubtitleRenderer *renderer, int w, int h); 55 | KIT_LOCAL void Kit_FlushSubtitleRendererBuffers(Kit_SubtitleRenderer *renderer); 56 | KIT_LOCAL void Kit_SignalSubtitleRenderer(Kit_SubtitleRenderer *renderer); 57 | KIT_LOCAL void Kit_CloseSubtitleRenderer(Kit_SubtitleRenderer *renderer); 58 | 59 | #endif // KITSUBRENDERER_H 60 | -------------------------------------------------------------------------------- /src/internal/subtitle/renderers/kitsubrenderer.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "kitchensink2/internal/subtitle/kitsubtitlepacket.h" 4 | #include "kitchensink2/internal/subtitle/renderers/kitsubrenderer.h" 5 | #include "kitchensink2/kiterror.h" 6 | 7 | Kit_SubtitleRenderer *Kit_CreateSubtitleRenderer( 8 | Kit_Decoder *decoder, 9 | renderer_render_cb render_cb, 10 | renderer_get_data_cb get_data_cb, 11 | renderer_get_raw_frames_cb get_raw_frames_cb, 12 | renderer_set_size_cb set_size_cb, 13 | renderer_flush_cb flush_cb, 14 | renderer_signal_cb signal_cb, 15 | renderer_close_cb close_cb, 16 | void *userdata 17 | ) { 18 | Kit_SubtitleRenderer *renderer = calloc(1, sizeof(Kit_SubtitleRenderer)); 19 | if(renderer == NULL) { 20 | Kit_SetError("Unable to allocate kit subtitle renderer"); 21 | return NULL; 22 | } 23 | renderer->decoder = decoder; 24 | renderer->render_cb = render_cb; 25 | renderer->get_raw_frames_cb = get_raw_frames_cb; 26 | renderer->close_cb = close_cb; 27 | renderer->get_data_cb = get_data_cb; 28 | renderer->set_size_cb = set_size_cb; 29 | renderer->flush_cb = flush_cb; 30 | renderer->signal_cb = signal_cb; 31 | renderer->userdata = userdata; 32 | return renderer; 33 | } 34 | 35 | void Kit_RunSubtitleRenderer(Kit_SubtitleRenderer *renderer, void *src, double pts, double start, double end) { 36 | if(renderer == NULL) 37 | return; 38 | if(renderer->render_cb != NULL) 39 | renderer->render_cb(renderer, src, pts, start, end); 40 | } 41 | 42 | void Kit_FlushSubtitleRendererBuffers(Kit_SubtitleRenderer *renderer) { 43 | if(renderer->flush_cb) 44 | renderer->flush_cb(renderer); 45 | } 46 | 47 | void Kit_SignalSubtitleRenderer(Kit_SubtitleRenderer *renderer) { 48 | if(renderer->signal_cb) 49 | renderer->signal_cb(renderer); 50 | } 51 | 52 | int Kit_GetSubtitleRendererSDLTexture( 53 | Kit_SubtitleRenderer *renderer, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 54 | ) { 55 | if(renderer == NULL) 56 | return 0; 57 | if(renderer->get_data_cb != NULL) 58 | return renderer->get_data_cb(renderer, atlas, texture, current_pts); 59 | return 0; 60 | } 61 | 62 | int Kit_GetSubtitleRendererRawFrames( 63 | Kit_SubtitleRenderer *renderer, unsigned char ***frames, SDL_Rect **sources, SDL_Rect **targets, double current_pts 64 | ) { 65 | if(renderer == NULL) 66 | return 0; 67 | if(renderer->get_raw_frames_cb != NULL) 68 | return renderer->get_raw_frames_cb(renderer, frames, sources, targets, current_pts); 69 | return 0; 70 | } 71 | 72 | void Kit_SetSubtitleRendererSize(Kit_SubtitleRenderer *renderer, int w, int h) { 73 | if(renderer == NULL) 74 | return; 75 | if(renderer->set_size_cb != NULL) 76 | renderer->set_size_cb(renderer, w, h); 77 | } 78 | 79 | void Kit_CloseSubtitleRenderer(Kit_SubtitleRenderer *renderer) { 80 | if(renderer == NULL) 81 | return; 82 | if(renderer->close_cb != NULL) 83 | renderer->close_cb(renderer); 84 | free(renderer); 85 | } 86 | -------------------------------------------------------------------------------- /include/kitchensink2/internal/kitdecoder.h: -------------------------------------------------------------------------------- 1 | #ifndef KITDECODER_H 2 | #define KITDECODER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "kitchensink2/kitcodec.h" 11 | #include "kitchensink2/kitconfig.h" 12 | #include "kitchensink2/kitformat.h" 13 | #include "kitchensink2/kitsource.h" 14 | #include "kitpacketbuffer.h" 15 | #include "kittimer.h" 16 | 17 | typedef struct Kit_Decoder Kit_Decoder; 18 | 19 | typedef enum Kit_DecoderInputResult 20 | { 21 | KIT_DEC_INPUT_OK = 0, 22 | KIT_DEC_INPUT_RETRY, 23 | KIT_DEC_INPUT_EOF, 24 | } Kit_DecoderInputResult; 25 | 26 | typedef Kit_DecoderInputResult (*dec_input_cb)(const Kit_Decoder *decoder, const AVPacket *packet); 27 | typedef bool (*dec_decode_cb)(const Kit_Decoder *decoder, double *pts); 28 | typedef void (*dec_flush_cb)(Kit_Decoder *decoder); 29 | typedef void (*dec_signal_cb)(Kit_Decoder *decoder); 30 | typedef void (*dec_close_cb)(Kit_Decoder *decoder); 31 | typedef void (*dec_get_buffers_cb)(const Kit_Decoder *decoder, unsigned int *length, unsigned int *capacity); 32 | 33 | struct Kit_Decoder { 34 | Kit_Timer *sync_timer; ///< Playback synchronization timer 35 | AVRational aspect_ratio; ///< Aspect ratio for the current frame (may change frame-to-frame) 36 | AVCodecContext *codec_ctx; ///< FFMpeg internal: Codec context 37 | AVStream *stream; ///< FFMpeg internal: Data stream 38 | enum AVPixelFormat hw_fmt; ///< FFMpeg internal: Hardware pixel format (if in use) 39 | enum AVHWDeviceType hw_type; ///< FFMpeg internal: Hardware device type (if in use) 40 | void *userdata; ///< Decoder specific information (Audio, video, subtitle context) 41 | dec_input_cb dec_input; ///< Decoder packet input function callback 42 | dec_decode_cb dec_decode; ///< Decoder decoding function callback 43 | dec_flush_cb dec_flush; ///< Decoder buffer flusher function callback 44 | dec_signal_cb dec_signal; ///< Decoder kill signal handler function callback (This is called before shutdown). 45 | dec_close_cb dec_close; ///< Decoder close function callback 46 | dec_get_buffers_cb dec_get_buffers; ///< Decoder buffer status getter callback 47 | }; 48 | 49 | KIT_LOCAL Kit_Decoder *Kit_CreateDecoder( 50 | AVStream *stream, 51 | Kit_Timer *sync_timer, 52 | int thread_count, 53 | unsigned int hw_device_types, 54 | dec_input_cb dec_input, 55 | dec_decode_cb dec_decode, 56 | dec_flush_cb dec_flush, 57 | dec_signal_cb dec_signal, 58 | dec_close_cb dec_close, 59 | dec_get_buffers_cb dec_get_buffers, 60 | void *userdata 61 | ); 62 | KIT_LOCAL void Kit_CloseDecoder(Kit_Decoder **dec); 63 | 64 | KIT_LOCAL int Kit_GetDecoderStreamIndex(const Kit_Decoder *decoder); 65 | KIT_LOCAL int Kit_GetDecoderCodecInfo(const Kit_Decoder *decoder, Kit_Codec *codec); 66 | 67 | KIT_LOCAL bool Kit_RunDecoder(const Kit_Decoder *decoder, double *pts); 68 | KIT_LOCAL Kit_DecoderInputResult Kit_AddDecoderPacket(const Kit_Decoder *decoder, const AVPacket *packet); 69 | KIT_LOCAL void Kit_ClearDecoderBuffers(Kit_Decoder *decoder); 70 | KIT_LOCAL void Kit_SignalDecoder(Kit_Decoder *decoder); 71 | KIT_LOCAL int Kit_GetDecoderBufferState(const Kit_Decoder *decoder, unsigned int *length, unsigned int *capacity); 72 | 73 | #endif // KITDECODER_H 74 | -------------------------------------------------------------------------------- /src/internal/video/kitvideoutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "kitchensink2/internal/video/kitvideoutils.h" 5 | 6 | static enum AVPixelFormat supported_list[] = { 7 | AV_PIX_FMT_YUV420P, 8 | AV_PIX_FMT_YUYV422, 9 | AV_PIX_FMT_UYVY422, 10 | AV_PIX_FMT_NV12, 11 | AV_PIX_FMT_NV21, 12 | AV_PIX_FMT_RGB24, 13 | AV_PIX_FMT_BGR24, 14 | AV_PIX_FMT_RGB555, 15 | AV_PIX_FMT_BGR555, 16 | AV_PIX_FMT_RGB565, 17 | AV_PIX_FMT_BGR565, 18 | AV_PIX_FMT_BGRA, 19 | AV_PIX_FMT_RGBA, 20 | AV_PIX_FMT_NONE 21 | }; 22 | 23 | enum AVPixelFormat Kit_FindBestAVPixelFormat(const enum AVPixelFormat fmt) 24 | { 25 | return avcodec_find_best_pix_fmt_of_list(supported_list, fmt, 1, NULL); 26 | } 27 | 28 | unsigned int Kit_FindSDLPixelFormat(const enum AVPixelFormat fmt) { 29 | switch(fmt) { 30 | case AV_PIX_FMT_YUV420P: 31 | return SDL_PIXELFORMAT_YV12; 32 | case AV_PIX_FMT_YUYV422: 33 | return SDL_PIXELFORMAT_YUY2; 34 | case AV_PIX_FMT_UYVY422: 35 | return SDL_PIXELFORMAT_UYVY; 36 | case AV_PIX_FMT_NV12: 37 | return SDL_PIXELFORMAT_NV12; 38 | case AV_PIX_FMT_NV21: 39 | return SDL_PIXELFORMAT_NV21; 40 | default: 41 | return SDL_PIXELFORMAT_RGBA32; 42 | } 43 | } 44 | 45 | Kit_HardwareDeviceType Kit_FindHWDeviceType(const enum AVHWDeviceType type) { 46 | switch(type) { 47 | case AV_HWDEVICE_TYPE_NONE: 48 | return KIT_HWDEVICE_TYPE_NONE; 49 | case AV_HWDEVICE_TYPE_VDPAU: 50 | return KIT_HWDEVICE_TYPE_VDPAU; 51 | case AV_HWDEVICE_TYPE_CUDA: 52 | return KIT_HWDEVICE_TYPE_CUDA; 53 | case AV_HWDEVICE_TYPE_VAAPI: 54 | return KIT_HWDEVICE_TYPE_VAAPI; 55 | case AV_HWDEVICE_TYPE_DXVA2: 56 | return KIT_HWDEVICE_TYPE_DXVA2; 57 | case AV_HWDEVICE_TYPE_QSV: 58 | return KIT_HWDEVICE_TYPE_QSV; 59 | case AV_HWDEVICE_TYPE_VIDEOTOOLBOX: 60 | return KIT_HWDEVICE_TYPE_VIDEOTOOLBOX; 61 | case AV_HWDEVICE_TYPE_D3D11VA: 62 | return KIT_HWDEVICE_TYPE_D3D11VA; 63 | case AV_HWDEVICE_TYPE_DRM: 64 | return KIT_HWDEVICE_TYPE_DRM; 65 | case AV_HWDEVICE_TYPE_OPENCL: 66 | return KIT_HWDEVICE_TYPE_OPENCL; 67 | case AV_HWDEVICE_TYPE_MEDIACODEC: 68 | return KIT_HWDEVICE_TYPE_MEDIACODEC; 69 | case AV_HWDEVICE_TYPE_VULKAN: 70 | return KIT_HWDEVICE_TYPE_VULKAN; 71 | default: 72 | return KIT_HWDEVICE_TYPE_NONE; 73 | } 74 | } 75 | 76 | enum AVPixelFormat Kit_FindAVPixelFormat(const unsigned int fmt) 77 | { 78 | switch(fmt) { 79 | case SDL_PIXELFORMAT_YV12: 80 | return AV_PIX_FMT_YUV420P; 81 | case SDL_PIXELFORMAT_YUY2: 82 | return AV_PIX_FMT_YUYV422; 83 | case SDL_PIXELFORMAT_UYVY: 84 | return AV_PIX_FMT_UYVY422; 85 | case SDL_PIXELFORMAT_NV12: 86 | return AV_PIX_FMT_NV12; 87 | case SDL_PIXELFORMAT_NV21: 88 | return AV_PIX_FMT_NV21; 89 | case SDL_PIXELFORMAT_ARGB32: 90 | return AV_PIX_FMT_ARGB; 91 | case SDL_PIXELFORMAT_RGBA32: 92 | return AV_PIX_FMT_RGBA; 93 | case SDL_PIXELFORMAT_BGR24: 94 | return AV_PIX_FMT_BGR24; 95 | case SDL_PIXELFORMAT_RGB24: 96 | return AV_PIX_FMT_RGB24; 97 | case SDL_PIXELFORMAT_RGB555: 98 | return AV_PIX_FMT_RGB555; 99 | case SDL_PIXELFORMAT_BGR555: 100 | return AV_PIX_FMT_BGR555; 101 | case SDL_PIXELFORMAT_RGB565: 102 | return AV_PIX_FMT_RGB565; 103 | case SDL_PIXELFORMAT_BGR565: 104 | return AV_PIX_FMT_BGR565; 105 | default: 106 | return AV_PIX_FMT_NONE; 107 | } 108 | } -------------------------------------------------------------------------------- /include/kitchensink2/kitformat.h: -------------------------------------------------------------------------------- 1 | #ifndef KITFORMAT_H 2 | #define KITFORMAT_H 3 | 4 | /** 5 | * @brief Audio/video output format type 6 | * 7 | * @file kitformat.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #include "kitchensink2/kitconfig.h" 17 | 18 | #define KIT_MAX_HW_DEVICES 32 19 | 20 | typedef enum 21 | { 22 | KIT_HWDEVICE_TYPE_NONE = 0, 23 | KIT_HWDEVICE_TYPE_VDPAU = 0x1, 24 | KIT_HWDEVICE_TYPE_CUDA = 0x2, 25 | KIT_HWDEVICE_TYPE_VAAPI = 0x4, 26 | KIT_HWDEVICE_TYPE_DXVA2 = 0x8, 27 | KIT_HWDEVICE_TYPE_QSV = 0x10, 28 | KIT_HWDEVICE_TYPE_VIDEOTOOLBOX = 0x20, 29 | KIT_HWDEVICE_TYPE_D3D11VA = 0x40, 30 | KIT_HWDEVICE_TYPE_DRM = 0x80, 31 | KIT_HWDEVICE_TYPE_OPENCL = 0x100, 32 | KIT_HWDEVICE_TYPE_MEDIACODEC = 0x200, 33 | KIT_HWDEVICE_TYPE_VULKAN = 0x400, 34 | KIT_HWDEVICE_TYPE_ALL = 0xFFFFFFFF, 35 | } Kit_HardwareDeviceType; 36 | 37 | /** 38 | * @brief Used to request specific type for formats for output video 39 | * 40 | * Note that any requests here will cause software conversion, which may be slow! 41 | */ 42 | typedef struct Kit_VideoFormatRequest { 43 | unsigned int 44 | hw_device_types; ///< Bitmap of allowed hardware acceleration types. Defaults to KIT_HWDEVICE_TYPE_ALL. 45 | unsigned int format; ///< Requested surface format. Defaults to SDL_PIXELFORMAT_UNKNOWN (allow any). 46 | int width; ///< Requested width in pixels. Defaults to -1 (no change). 47 | int height; ///< Requested height in pixels. Defaults to -1 (no change). 48 | } Kit_VideoFormatRequest; 49 | 50 | /** 51 | * @brief Sets default values for Kit_VideoFormatRequest 52 | * 53 | * @param request Request to reset to default values 54 | */ 55 | KIT_API void Kit_ResetVideoFormatRequest(Kit_VideoFormatRequest *request); 56 | 57 | /** 58 | * @brief Used to request specific type for formats for output audio 59 | * 60 | * Note that any requests here will cause software conversion, which may be slow! 61 | */ 62 | typedef struct Kit_AudioFormatRequest { 63 | unsigned int format; ///< Requested sample format. Defaults to 0 (no change). 64 | int is_signed; ///< Signedness, 1 = signed, 0 = unsigned. Defaults to -1 (no change). 65 | int bytes; ///< Bytes per sample per channel. Defaults to -1 (no change). 66 | int sample_rate; ///< Sampling rate. Defaults to -1 (no change). 67 | int channels; ///< Channels. Defaults to -1 (no change). 68 | } Kit_AudioFormatRequest; 69 | 70 | /** 71 | * @brief Sets default values for Kit_AudioFormatRequest 72 | * 73 | * @param request Request to reset to default values 74 | */ 75 | KIT_API void Kit_ResetAudioFormatRequest(Kit_AudioFormatRequest *request); 76 | 77 | /** 78 | * @brief Contains information about the subtitle data format coming out from the player 79 | */ 80 | typedef struct Kit_SubtitleOutputFormat { 81 | unsigned int format; ///< SDL_PixelFormat for surface format description 82 | } Kit_SubtitleOutputFormat; 83 | 84 | /** 85 | * @brief Contains information about the video data format coming out from the player 86 | */ 87 | typedef struct Kit_VideoOutputFormat { 88 | unsigned int hw_device_type; ///< Kit_HardwareDeviceType, if enabled. 89 | unsigned int format; ///< SDL_PixelFormat for surface format description 90 | int width; ///< Width in pixels 91 | int height; ///< Height in pixels 92 | } Kit_VideoOutputFormat; 93 | 94 | /** 95 | * @brief Contains information about the audio data format coming out from the player 96 | */ 97 | typedef struct Kit_AudioOutputFormat { 98 | unsigned int format; ///< SDL_AudioFormat for format description 99 | int is_signed; ///< Signedness, 1 = signed, 0 = unsigned 100 | int bytes; ///< Bytes per sample per channel 101 | int sample_rate; ///< Sampling rate 102 | int channels; ///< Channels 103 | } Kit_AudioOutputFormat; 104 | 105 | #ifdef __cplusplus 106 | } 107 | #endif 108 | 109 | #endif // KITFORMAT_H 110 | -------------------------------------------------------------------------------- /src/internal/kitdecoderthread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "kitchensink2/internal/kitdecoder.h" 5 | #include "kitchensink2/internal/kitdecoderthread.h" 6 | #include "kitchensink2/internal/utils/kitlog.h" 7 | #include "kitchensink2/kiterror.h" 8 | 9 | static void Kit_ProcessPacket(Kit_DecoderThread *thread, bool *pts_jumped, bool *eof_received) { 10 | Kit_DecoderInputResult ret; 11 | bool is_eof; 12 | 13 | if(!Kit_BeginPacketBufferRead(thread->input, thread->scratch_packet, 100)) 14 | return; 15 | 16 | // If a valid packet was found, first check if it's a control packet. Value 1 means seek. 17 | // Seek packet is created in the demuxer, and is sent after avformat_seek_file() is called. 18 | if(thread->scratch_packet->opaque == (void *)1) { 19 | Kit_ClearDecoderBuffers(thread->decoder); 20 | *pts_jumped = true; 21 | goto finish; 22 | } 23 | is_eof = thread->scratch_packet->opaque == (void *)2; 24 | 25 | // If valid packet was found and it is not a control packet, it must contain stream data. 26 | // Attempt to add it to the ffmpeg decoder internal queue. Note that the queue may be full, in which case 27 | // we try again later. 28 | ret = Kit_AddDecoderPacket(thread->decoder, is_eof ? NULL : thread->scratch_packet); 29 | if(ret == KIT_DEC_INPUT_RETRY) 30 | goto cancel; 31 | if(ret == KIT_DEC_INPUT_EOF || is_eof) 32 | *eof_received = true; 33 | 34 | finish: 35 | Kit_FinishPacketBufferRead(thread->input); 36 | av_packet_unref(thread->scratch_packet); 37 | return; 38 | 39 | cancel: 40 | Kit_CancelPacketBufferRead(thread->input); 41 | av_packet_unref(thread->scratch_packet); 42 | return; 43 | } 44 | 45 | static int Kit_DecodeMain(void *ptr) { 46 | Kit_DecoderThread *thread = ptr; 47 | bool pts_jumped = false; 48 | bool eof_received = false; 49 | double pts; 50 | 51 | while(SDL_AtomicGet(&thread->run)) { 52 | Kit_ProcessPacket(thread, &pts_jumped, &eof_received); 53 | 54 | // Run the decoder. This will consume packets from the ffmpeg queue. We may need to call this multiple times, 55 | // since a single data packet might contain multiple frames. 56 | while(SDL_AtomicGet(&thread->run) && Kit_RunDecoder(thread->decoder, &pts)) { 57 | if(pts_jumped) { 58 | // Note that we change the sync a bit to give decoders some time to decode. 59 | // The 0.1 is essentially a hack that moves the sync time forwards a bit, so that the data getter 60 | // functions wait a little bit before they start feeding again. 61 | Kit_AdjustTimerBase(thread->decoder->sync_timer, pts - 0.1); 62 | pts_jumped = false; 63 | } 64 | } 65 | if(eof_received) 66 | break; 67 | } 68 | 69 | SDL_AtomicSet(&thread->run, 0); 70 | return 0; 71 | } 72 | 73 | Kit_DecoderThread *Kit_CreateDecoderThread(Kit_PacketBuffer *input, Kit_Decoder *decoder) { 74 | Kit_DecoderThread *decoder_thread; 75 | AVPacket *packet; 76 | 77 | if((packet = av_packet_alloc()) == NULL) { 78 | Kit_SetError("Unable to allocate decoder scratch packet"); 79 | goto error_0; 80 | } 81 | if((decoder_thread = calloc(1, sizeof(Kit_DecoderThread))) == NULL) { 82 | Kit_SetError("Unable to allocate decoder thread"); 83 | goto error_1; 84 | } 85 | 86 | decoder_thread->input = input; 87 | decoder_thread->decoder = decoder; 88 | decoder_thread->scratch_packet = packet; 89 | SDL_AtomicSet(&decoder_thread->run, 0); 90 | return decoder_thread; 91 | 92 | error_1: 93 | av_packet_free(&packet); 94 | error_0: 95 | return NULL; 96 | } 97 | 98 | void Kit_StartDecoderThread(Kit_DecoderThread *decoder_thread, const char *name) { 99 | if(!decoder_thread || decoder_thread->thread) 100 | return; 101 | SDL_AtomicSet(&decoder_thread->run, 1); 102 | decoder_thread->thread = SDL_CreateThread(Kit_DecodeMain, name, decoder_thread); 103 | } 104 | 105 | void Kit_StopDecoderThread(Kit_DecoderThread *decoder_thread) { 106 | if(!decoder_thread || !decoder_thread->thread) 107 | return; 108 | SDL_AtomicSet(&decoder_thread->run, 0); 109 | } 110 | 111 | void Kit_WaitDecoderThread(Kit_DecoderThread *decoder_thread) { 112 | if(!decoder_thread || !decoder_thread->thread) 113 | return; 114 | SDL_WaitThread(decoder_thread->thread, NULL); 115 | decoder_thread->thread = NULL; 116 | } 117 | 118 | bool Kit_IsDecoderThreadAlive(Kit_DecoderThread *decoder_thread) { 119 | return SDL_AtomicGet(&decoder_thread->run); 120 | } 121 | 122 | void Kit_CloseDecoderThread(Kit_DecoderThread **ref) { 123 | if(!ref || !*ref) 124 | return; 125 | Kit_DecoderThread *decoder_thread = *ref; 126 | Kit_StopDecoderThread(decoder_thread); 127 | Kit_WaitDecoderThread(decoder_thread); 128 | av_packet_free(&decoder_thread->scratch_packet); 129 | free(decoder_thread); 130 | *ref = NULL; 131 | } 132 | -------------------------------------------------------------------------------- /include/kitchensink2/kitlib.h: -------------------------------------------------------------------------------- 1 | #ifndef KITLIB_H 2 | #define KITLIB_H 3 | 4 | /** 5 | * @brief Library initialization and deinitialization functionality 6 | * 7 | * @file kitlib.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-25 10 | */ 11 | 12 | #include "kitchensink2/kitconfig.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | /** 19 | * @brief Font hinting options. Used as values for Kit_SetHint(KIT_HINT_FONT_HINTING, ...). 20 | */ 21 | enum 22 | { 23 | KIT_FONT_HINTING_NONE = 0, ///< No hinting. This is recommended option 24 | KIT_FONT_HINTING_LIGHT, ///< Light hinting. Use this if you need hinting 25 | KIT_FONT_HINTING_NORMAL, ///< Not recommended, please see libass docs for details 26 | KIT_FONT_HINTING_NATIVE, ///< Not recommended, please see libass docs for details 27 | KIT_FONT_HINTING_COUNT 28 | }; 29 | 30 | /** 31 | * @brief SDL_kitchensink library version container 32 | */ 33 | typedef struct Kit_Version { 34 | unsigned char major; ///< Major version number, raising this signifies API breakage 35 | unsigned char minor; ///< Minor version number, small/internal changes 36 | unsigned char patch; ///< Patch version number, bugfixes etc. 37 | } Kit_Version; 38 | 39 | /** 40 | * @brief Library hint types. Used as keys for Kit_SetHint(). 41 | * 42 | * Note that all of these must be set *before* player initialization for them to take effect! 43 | */ 44 | typedef enum Kit_HintType 45 | { 46 | KIT_HINT_FONT_HINTING, ///< Set font hinting mode (currently used for libass) 47 | KIT_HINT_THREAD_COUNT, ///< Set thread count for ffmpeg (default: 0 for autodetect) 48 | KIT_HINT_VIDEO_BUFFER_PACKETS, ///< Video input buffer packets (default: 16) 49 | KIT_HINT_AUDIO_BUFFER_PACKETS, ///< Audio input buffer packets (default: 64) 50 | KIT_HINT_SUBTITLE_BUFFER_PACKETS, ///< Subtitle input buffer packets (default: 64) 51 | KIT_HINT_VIDEO_BUFFER_FRAMES, ///< Video output buffer frames (default: 2) 52 | KIT_HINT_AUDIO_BUFFER_FRAMES, ///< Audio output buffers (default: 64) 53 | KIT_HINT_SUBTITLE_BUFFER_FRAMES, ///< Subtitle output buffers (default: 64) 54 | KIT_HINT_VIDEO_LATE_THRESHOLD, ///< Late threshold for video frames in milliseconds (default: 50ms) 55 | KIT_HINT_VIDEO_EARLY_THRESHOLD, ///< Early threshold for video frames in milliseconds (default: 5ms) 56 | KIT_HINT_AUDIO_LATE_THRESHOLD, ///< Late threshold for audio frames in milliseconds (default: 50ms) 57 | KIT_HINT_AUDIO_EARLY_THRESHOLD, ///< Early threshold for audio frames in milliseconds (default: 30ms) 58 | } Kit_HintType; 59 | 60 | /** 61 | * @brief Library initialization options, please see Kit_Init() 62 | */ 63 | enum 64 | { 65 | KIT_INIT_NETWORK = 0x1, ///< Initialise ffmpeg network support 66 | KIT_INIT_ASS = 0x2, ///< Initialize libass support (library must be linked statically or loadable dynamically) 67 | KIT_INIT_HW_DECODE = 0x4, ///< Enable hardware decoding 68 | }; 69 | 70 | /** 71 | * @brief Initialize SDL_kitchensink library. 72 | * 73 | * This MUST be run before doing anything. After you are done using the library, you should use Kit_Quit() to 74 | * deinitialize everything. Otherwise there might be resource leaks. 75 | * 76 | * Following flags can be used to initialize subsystems: 77 | * - `KIT_INIT_NETWORK` for ffmpeg network support (playback from the internet, for example) 78 | * - `KIT_INIT_ASS` for libass subtitles (text and ass/ssa subtitle support) 79 | * - `KIT_INIT_HW_DECODE` to enable hardware decoding capabilities. Hardware is picked automatically. 80 | * 81 | * Note that if this function fails, the failure reason should be available via Kit_GetError(). 82 | * 83 | * For example: 84 | * ``` 85 | * if(Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS|KIT_INIT_HW_DECODE) != 0) { 86 | * fprintf(stderr, "Error: %s\n", Kit_GetError()); 87 | * return 1; 88 | * } 89 | * ``` 90 | * 91 | * @param flags Library initialization flags 92 | * @return Returns 0 on success, 1 on failure. 93 | */ 94 | KIT_API int Kit_Init(unsigned int flags); 95 | 96 | /** 97 | * @brief Deinitializes SDL_kitchensink 98 | * 99 | * Note that any calls to library functions after this will cause undefined behaviour! 100 | */ 101 | KIT_API void Kit_Quit(); 102 | 103 | /** 104 | * @brief Sets a library-wide hint 105 | * 106 | * This can be used to set hints on how the library should behave. See Kit_HintType 107 | * for all the options. 108 | * 109 | * @param type Hint type (refer to Kit_HintType for options) 110 | * @param value Value for the hint 111 | */ 112 | KIT_API void Kit_SetHint(Kit_HintType type, int value); 113 | 114 | /** 115 | * @brief Gets a previously set or default hint value 116 | * 117 | * @param type Hint type (refer to Kit_HintType for options) 118 | * @return Hint value 119 | */ 120 | KIT_API int Kit_GetHint(Kit_HintType type); 121 | 122 | /** 123 | * @brief Can be used to fetch the version of the linked SDL_kitchensink library 124 | * 125 | * @param version Allocated Kit_Version 126 | */ 127 | KIT_API void Kit_GetVersion(Kit_Version *version); 128 | 129 | #ifdef __cplusplus 130 | } 131 | #endif 132 | 133 | #endif // KITLIB_H 134 | -------------------------------------------------------------------------------- /src/kitlib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef USE_DYNAMIC_LIBASS 3 | #include 4 | #endif 5 | 6 | #include "libavcodec/avcodec.h" 7 | #include 8 | 9 | #include "kitchensink2/internal/kitlibstate.h" 10 | #include "kitchensink2/internal/utils/kithelpers.h" 11 | #include "kitchensink2/kitchensink.h" 12 | 13 | static void _libass_msg_callback(int level, const char *fmt, va_list va, void *data) { 14 | } 15 | 16 | int Kit_InitASS(Kit_LibraryState *state) { 17 | #ifdef USE_DYNAMIC_LIBASS 18 | state->ass_so_handle = SDL_LoadObject(DYNAMIC_LIBASS_NAME); 19 | if(state->ass_so_handle == NULL) { 20 | Kit_SetError("Unable to load ASS library"); 21 | return 1; 22 | } 23 | load_libass(state->ass_so_handle); 24 | #endif 25 | state->libass_handle = ass_library_init(); 26 | ass_set_message_cb(state->libass_handle, _libass_msg_callback, NULL); 27 | return 0; 28 | } 29 | 30 | void Kit_CloseASS(Kit_LibraryState *state) { 31 | ass_library_done(state->libass_handle); 32 | state->libass_handle = NULL; 33 | #ifdef USE_DYNAMIC_LIBASS 34 | SDL_UnloadObject(state->ass_so_handle); 35 | state->ass_so_handle = NULL; 36 | #endif 37 | } 38 | 39 | int Kit_Init(unsigned int flags) { 40 | Kit_LibraryState *state = Kit_GetLibraryState(); 41 | 42 | if(state->init_flags != 0) { 43 | Kit_SetError("SDL_kitchensink is already initialized"); 44 | goto exit_0; 45 | } 46 | if(flags & KIT_INIT_NETWORK) { 47 | avformat_network_init(); 48 | } 49 | if(flags & KIT_INIT_ASS) { 50 | if(Kit_InitASS(state) != 0) { 51 | Kit_SetError("Failed to initialize libass"); 52 | goto exit_1; 53 | } 54 | } 55 | 56 | // Disable ffmpeg logging. 57 | av_log_set_level(AV_LOG_QUIET); 58 | 59 | state->init_flags = flags; 60 | return 0; 61 | 62 | exit_1: 63 | avformat_network_deinit(); 64 | exit_0: 65 | return 1; 66 | } 67 | 68 | void Kit_Quit() { 69 | Kit_LibraryState *state = Kit_GetLibraryState(); 70 | if(state->init_flags & KIT_INIT_NETWORK) { 71 | avformat_network_deinit(); 72 | } 73 | if(state->init_flags & KIT_INIT_ASS) { 74 | Kit_CloseASS(state); 75 | } 76 | state->init_flags = 0; 77 | } 78 | 79 | void Kit_SetHint(Kit_HintType type, int value) { 80 | Kit_LibraryState *state = Kit_GetLibraryState(); 81 | switch(type) { 82 | case KIT_HINT_THREAD_COUNT: 83 | state->thread_count = Kit_max(value, 0); 84 | break; 85 | case KIT_HINT_FONT_HINTING: 86 | state->font_hinting = Kit_max(Kit_min(value, KIT_FONT_HINTING_COUNT - 1), 0); 87 | break; 88 | case KIT_HINT_VIDEO_BUFFER_PACKETS: 89 | state->video_packet_buffer_size = Kit_max(value, 1); 90 | break; 91 | case KIT_HINT_AUDIO_BUFFER_PACKETS: 92 | state->audio_packet_buffer_size = Kit_max(value, 1); 93 | break; 94 | case KIT_HINT_SUBTITLE_BUFFER_PACKETS: 95 | state->subtitle_packet_buffer_size = Kit_max(value, 1); 96 | break; 97 | case KIT_HINT_VIDEO_BUFFER_FRAMES: 98 | state->video_frame_buffer_size = Kit_max(value, 1); 99 | break; 100 | case KIT_HINT_AUDIO_BUFFER_FRAMES: 101 | state->audio_frame_buffer_size = Kit_max(value, 1); 102 | break; 103 | case KIT_HINT_SUBTITLE_BUFFER_FRAMES: 104 | state->subtitle_frame_buffer_size = Kit_max(value, 1); 105 | break; 106 | case KIT_HINT_AUDIO_EARLY_THRESHOLD: 107 | state->audio_early_threshold = Kit_max(value, 0); 108 | break; 109 | case KIT_HINT_AUDIO_LATE_THRESHOLD: 110 | state->audio_late_threshold = Kit_max(value, 0); 111 | break; 112 | case KIT_HINT_VIDEO_EARLY_THRESHOLD: 113 | state->video_early_threshold = Kit_max(value, 0); 114 | break; 115 | case KIT_HINT_VIDEO_LATE_THRESHOLD: 116 | state->video_late_threshold = Kit_max(value, 0); 117 | break; 118 | } 119 | } 120 | 121 | int Kit_GetHint(Kit_HintType type) { 122 | const Kit_LibraryState *state = Kit_GetLibraryState(); 123 | switch(type) { 124 | case KIT_HINT_THREAD_COUNT: 125 | return state->thread_count; 126 | case KIT_HINT_FONT_HINTING: 127 | return state->font_hinting; 128 | case KIT_HINT_VIDEO_BUFFER_PACKETS: 129 | return state->video_packet_buffer_size; 130 | case KIT_HINT_AUDIO_BUFFER_PACKETS: 131 | return state->audio_packet_buffer_size; 132 | case KIT_HINT_SUBTITLE_BUFFER_PACKETS: 133 | return state->subtitle_packet_buffer_size; 134 | case KIT_HINT_VIDEO_BUFFER_FRAMES: 135 | return state->video_frame_buffer_size; 136 | case KIT_HINT_AUDIO_BUFFER_FRAMES: 137 | return state->audio_frame_buffer_size; 138 | case KIT_HINT_SUBTITLE_BUFFER_FRAMES: 139 | return state->subtitle_frame_buffer_size; 140 | case KIT_HINT_AUDIO_EARLY_THRESHOLD: 141 | return state->audio_early_threshold; 142 | case KIT_HINT_AUDIO_LATE_THRESHOLD: 143 | return state->audio_late_threshold; 144 | case KIT_HINT_VIDEO_EARLY_THRESHOLD: 145 | return state->video_early_threshold; 146 | case KIT_HINT_VIDEO_LATE_THRESHOLD: 147 | return state->video_late_threshold; 148 | default: 149 | return 0; 150 | } 151 | } 152 | 153 | void Kit_GetVersion(Kit_Version *version) { 154 | assert(version != NULL); 155 | version->major = KIT_VERSION_MAJOR; 156 | version->minor = KIT_VERSION_MINOR; 157 | version->patch = KIT_VERSION_PATCH; 158 | } 159 | -------------------------------------------------------------------------------- /src/internal/subtitle/kitatlas.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "kitchensink2/internal/subtitle/kitatlas.h" 4 | #include "kitchensink2/internal/utils/kithelpers.h" 5 | 6 | Kit_TextureAtlas *Kit_CreateAtlas() { 7 | Kit_TextureAtlas *atlas = calloc(1, sizeof(Kit_TextureAtlas)); 8 | if(atlas == NULL) { 9 | goto EXIT_0; 10 | } 11 | atlas->cur_items = 0; 12 | atlas->max_items = 1024; 13 | atlas->max_shelves = 256; 14 | atlas->w = 0; 15 | atlas->h = 0; 16 | 17 | // Allocate items. These hold the surfaces that should be in atlas 18 | atlas->items = calloc(atlas->max_items, sizeof(Kit_TextureAtlasItem)); 19 | if(atlas->items == NULL) { 20 | goto EXIT_1; 21 | } 22 | 23 | // Allocate shelves. These describe the used space of the atlas 24 | atlas->shelves = calloc(atlas->max_shelves, sizeof(Kit_Shelf)); 25 | if(atlas->shelves == NULL) { 26 | goto EXIT_2; 27 | } 28 | 29 | return atlas; 30 | 31 | EXIT_2: 32 | free(atlas->items); 33 | EXIT_1: 34 | free(atlas); 35 | EXIT_0: 36 | return NULL; 37 | } 38 | 39 | void Kit_ClearAtlasContent(Kit_TextureAtlas *atlas) { 40 | atlas->cur_items = 0; 41 | memset(atlas->items, 0, atlas->max_items * sizeof(Kit_TextureAtlasItem)); 42 | memset(atlas->shelves, 0, atlas->max_shelves * sizeof(Kit_Shelf)); 43 | } 44 | 45 | void Kit_FreeAtlas(Kit_TextureAtlas *atlas) { 46 | assert(atlas != NULL); 47 | free(atlas->items); 48 | free(atlas->shelves); 49 | free(atlas); 50 | } 51 | 52 | void Kit_SetItemAllocation(Kit_TextureAtlasItem *item, const SDL_Surface *surface, int x, int y) { 53 | assert(item != NULL); 54 | 55 | item->source.x = x; 56 | item->source.y = y; 57 | item->source.w = surface->w; 58 | item->source.h = surface->h; 59 | } 60 | 61 | int Kit_FindFreeAtlasSlot(const Kit_TextureAtlas *atlas, const SDL_Surface *surface, Kit_TextureAtlasItem *item) { 62 | assert(atlas != NULL); 63 | assert(item != NULL); 64 | 65 | int shelf_w; 66 | int shelf_h; 67 | int total_remaining_h = atlas->h; 68 | int total_reserved_h = 0; 69 | int best_shelf_idx = -1; 70 | int best_shelf_h = atlas->h; 71 | int best_shelf_y = 0; 72 | 73 | // Try to find a good, existing shelf to put this item in. 74 | int shelf_idx; 75 | for(shelf_idx = 0; shelf_idx < atlas->max_shelves; shelf_idx++) { 76 | shelf_w = atlas->shelves[shelf_idx].width; 77 | shelf_h = atlas->shelves[shelf_idx].height; 78 | if(shelf_h == 0) { 79 | break; 80 | } 81 | total_remaining_h -= shelf_h; 82 | total_reserved_h += shelf_h; 83 | 84 | // If the item fits, check if the space is better than previous one 85 | if(surface->w <= (atlas->w - shelf_w) && surface->h <= shelf_h && shelf_h < best_shelf_h) { 86 | best_shelf_h = shelf_h; 87 | best_shelf_idx = shelf_idx; 88 | best_shelf_y = total_reserved_h - shelf_h; 89 | } 90 | } 91 | 92 | // If existing shelf found, put the item there. Otherwise, create a new shelf. 93 | if(best_shelf_idx != -1) { 94 | Kit_SetItemAllocation(item, surface, atlas->shelves[best_shelf_idx].width, best_shelf_y); 95 | atlas->shelves[best_shelf_idx].width += surface->w; 96 | atlas->shelves[best_shelf_idx].count += 1; 97 | return 0; 98 | } else if(total_remaining_h >= surface->h) { 99 | atlas->shelves[shelf_idx].width = surface->w; 100 | atlas->shelves[shelf_idx].height = surface->h; 101 | atlas->shelves[shelf_idx].count = 1; 102 | Kit_SetItemAllocation(item, surface, 0, total_reserved_h); 103 | return 0; 104 | } 105 | 106 | return 1; // Can't fit! 107 | } 108 | 109 | void Kit_CheckAtlasTextureSize(Kit_TextureAtlas *atlas, SDL_Texture *texture) { 110 | assert(atlas != NULL); 111 | assert(texture != NULL); 112 | 113 | // Check if texture size has changed, and clear content if it has. 114 | int texture_w; 115 | int texture_h; 116 | if(SDL_QueryTexture(texture, NULL, NULL, &texture_w, &texture_h) == 0) { 117 | atlas->w = texture_w; 118 | atlas->h = texture_h; 119 | } 120 | } 121 | 122 | int Kit_GetAtlasItems(const Kit_TextureAtlas *atlas, SDL_Rect *sources, SDL_Rect *targets, int limit) { 123 | assert(atlas != NULL); 124 | assert(limit >= 0); 125 | const Kit_TextureAtlasItem *item = NULL; 126 | 127 | int max_count = Kit_min(atlas->cur_items, limit); 128 | for(int i = 0; i < max_count; i++) { 129 | item = &atlas->items[i]; 130 | if(sources != NULL) 131 | memcpy(&sources[i], &item->source, sizeof(SDL_Rect)); 132 | if(targets != NULL) 133 | memcpy(&targets[i], &item->target, sizeof(SDL_Rect)); 134 | } 135 | return max_count; 136 | } 137 | 138 | int Kit_AddAtlasItem( 139 | Kit_TextureAtlas *atlas, SDL_Texture *texture, const SDL_Surface *surface, const SDL_Rect *target 140 | ) { 141 | assert(atlas != NULL); 142 | assert(surface != NULL); 143 | assert(target != NULL); 144 | 145 | // Make sure there is still room 146 | if(atlas->cur_items >= atlas->max_items) 147 | return -1; 148 | 149 | // Create a new item 150 | Kit_TextureAtlasItem item; 151 | memset(&item, 0, sizeof(Kit_TextureAtlasItem)); 152 | memcpy(&item.target, target, sizeof(SDL_Rect)); 153 | 154 | // Allocate space for the new item 155 | if(Kit_FindFreeAtlasSlot(atlas, surface, &item) != 0) { 156 | return -1; 157 | } 158 | 159 | // And update texture with the surface 160 | SDL_UpdateTexture(texture, &item.source, surface->pixels, surface->pitch); 161 | 162 | // Room found, add item to the atlas 163 | memcpy(&atlas->items[atlas->cur_items++], &item, sizeof(Kit_TextureAtlasItem)); 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /examples/example_audio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* 8 | * Note! This example does not do proper error handling etc. 9 | * It is for example use only! 10 | */ 11 | 12 | #define AUDIO_BUFFER_SIZE (1024 * 64) 13 | #define AUDIO_BUFFER_PACKETS 24 14 | #define AUDIO_BUFFER_FRAMES 32 15 | 16 | int main(int argc, char *argv[]) { 17 | int err = 0, ret = 0; 18 | const char *filename = NULL; 19 | 20 | // Events 21 | bool run = true; 22 | 23 | // Kitchensink 24 | Kit_Source *src = NULL; 25 | Kit_Player *player = NULL; 26 | 27 | // Audio playback 28 | SDL_AudioSpec wanted_spec, audio_spec; 29 | SDL_AudioDeviceID audio_dev; 30 | char audio_buf[AUDIO_BUFFER_SIZE]; 31 | 32 | // Get filename to open 33 | if(argc != 2) { 34 | fprintf(stderr, "Usage: audio \n"); 35 | return 0; 36 | } 37 | filename = argv[1]; 38 | 39 | // Init SDL 40 | err = SDL_Init(SDL_INIT_AUDIO); 41 | if(err != 0) { 42 | fprintf(stderr, "Unable to initialize SDL!\n"); 43 | return 1; 44 | } 45 | 46 | err = Kit_Init(KIT_INIT_NETWORK); 47 | if(err != 0) { 48 | fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); 49 | return 1; 50 | } 51 | 52 | // Set input and output buffering to reduce latency 53 | Kit_SetHint(KIT_HINT_AUDIO_BUFFER_FRAMES, AUDIO_BUFFER_PACKETS); 54 | Kit_SetHint(KIT_HINT_AUDIO_BUFFER_PACKETS, AUDIO_BUFFER_FRAMES); 55 | 56 | // Loosen up sync thresholds -- this should help with stuttering in network streams. 57 | Kit_SetHint(KIT_HINT_AUDIO_EARLY_THRESHOLD, 30); 58 | Kit_SetHint(KIT_HINT_AUDIO_LATE_THRESHOLD, 100); 59 | 60 | // Open up the sourcefile. 61 | src = Kit_CreateSourceFromUrl(filename); 62 | if(src == NULL) { 63 | fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); 64 | return 1; 65 | } 66 | 67 | // Print stream information 68 | Kit_SourceStreamInfo source_info; 69 | fprintf(stderr, "Source streams:\n"); 70 | for(int i = 0; i < Kit_GetSourceStreamCount(src); i++) { 71 | err = Kit_GetSourceStreamInfo(src, &source_info, i); 72 | if(err) { 73 | fprintf(stderr, "Unable to fetch stream #%d information: %s.\n", i, Kit_GetError()); 74 | return 1; 75 | } 76 | fprintf(stderr, " * Stream #%d: %s\n", i, Kit_GetKitStreamTypeString(source_info.type)); 77 | } 78 | 79 | // Create the player. No video, pick best audio stream, no subtitles, no screen 80 | player = Kit_CreatePlayer(src, -1, Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO), -1, NULL, NULL, 0, 0); 81 | if(player == NULL) { 82 | fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); 83 | return 1; 84 | } 85 | 86 | // Print some information 87 | Kit_PlayerInfo player_info; 88 | Kit_GetPlayerInfo(player, &player_info); 89 | 90 | // Make sure there is audio in the file to play first. 91 | if(Kit_GetPlayerAudioStream(player) == -1) { 92 | fprintf(stderr, "File contains no audio!\n"); 93 | return 1; 94 | } 95 | 96 | fprintf(stderr, "Media information:\n"); 97 | fprintf( 98 | stderr, 99 | " * Audio: %s (%s), %dHz, %dch, %db, %s\n", 100 | player_info.audio_codec.name, 101 | player_info.audio_codec.description, 102 | player_info.audio_format.sample_rate, 103 | player_info.audio_format.channels, 104 | player_info.audio_format.bytes, 105 | player_info.audio_format.is_signed ? "signed" : "unsigned" 106 | ); 107 | 108 | // Init audio 109 | SDL_memset(&wanted_spec, 0, sizeof(wanted_spec)); 110 | wanted_spec.freq = player_info.audio_format.sample_rate; 111 | wanted_spec.format = player_info.audio_format.format; 112 | wanted_spec.channels = player_info.audio_format.channels; 113 | audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0); 114 | SDL_PauseAudioDevice(audio_dev, 0); 115 | 116 | // Flush output just in case 117 | fflush(stderr); 118 | 119 | // Start playback 120 | Kit_PlayerPlay(player); 121 | 122 | bool is_buffering = false; 123 | while(run) { 124 | if(Kit_GetPlayerState(player) == KIT_STOPPED) { 125 | run = false; 126 | continue; 127 | } 128 | 129 | // Handle SDL events so that the application reacts to input. 130 | SDL_Event event; 131 | while(SDL_PollEvent(&event)) { 132 | switch(event.type) { 133 | case SDL_QUIT: 134 | run = false; 135 | break; 136 | } 137 | } 138 | 139 | // If audio buffers fall below given threshold, pause playback and start buffering. 140 | if (!is_buffering) { 141 | if(!Kit_HasBufferFillRate(player, -1, 10, -1, -1)) { 142 | Kit_PlayerPause(player); 143 | is_buffering = true; 144 | } 145 | } else { 146 | if(Kit_WaitBufferFillRate(player, 50, 50, -1, -1, 1.0) == 1) { 147 | fprintf(stderr, "Buffering ...\n"); 148 | } else { 149 | is_buffering = false; 150 | Kit_PlayerPlay(player); 151 | } 152 | } 153 | 154 | // Fetch as many audio samples as the decoder is willing to give. 155 | int queued; 156 | do { 157 | queued = SDL_GetQueuedAudioSize(audio_dev); 158 | ret = Kit_GetPlayerAudioData(player, UINT_MAX, (unsigned char *)audio_buf, AUDIO_BUFFER_SIZE - queued); 159 | if(ret > 0) { 160 | SDL_QueueAudio(audio_dev, audio_buf, ret); 161 | } 162 | } while(ret > 0 && queued < AUDIO_BUFFER_SIZE); 163 | 164 | SDL_Delay(1); 165 | } 166 | 167 | Kit_ClosePlayer(player); 168 | Kit_CloseSource(src); 169 | Kit_Quit(); 170 | 171 | SDL_CloseAudioDevice(audio_dev); 172 | SDL_Quit(); 173 | return 0; 174 | } 175 | -------------------------------------------------------------------------------- /src/kitutils.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "kitchensink2/kitformat.h" 4 | #include "kitchensink2/kitsource.h" 5 | #include "kitchensink2/kitutils.h" 6 | 7 | const char *Kit_GetSDLAudioFormatString(unsigned int type) { 8 | switch(type) { 9 | case AUDIO_S8: 10 | return "AUDIO_S8"; 11 | case AUDIO_U8: 12 | return "AUDIO_U8"; 13 | case AUDIO_S16: 14 | return "AUDIO_S16"; 15 | case AUDIO_U16: 16 | return "AUDIO_U16"; 17 | case AUDIO_S32: 18 | return "AUDIO_S32"; 19 | case AUDIO_F32: 20 | return "AUDIO_F32"; 21 | default: 22 | return NULL; 23 | } 24 | } 25 | 26 | const char *Kit_GetSDLPixelFormatString(unsigned int type) { 27 | switch(type) { 28 | case SDL_PIXELFORMAT_UNKNOWN: 29 | return "SDL_PIXELFORMAT_UNKNOWN"; 30 | case SDL_PIXELFORMAT_INDEX1LSB: 31 | return "SDL_PIXELFORMAT_INDEX1LSB"; 32 | case SDL_PIXELFORMAT_INDEX1MSB: 33 | return "SDL_PIXELFORMAT_INDEX1MSB"; 34 | case SDL_PIXELFORMAT_INDEX4LSB: 35 | return "SDL_PIXELFORMAT_INDEX4LSB"; 36 | case SDL_PIXELFORMAT_INDEX4MSB: 37 | return "SDL_PIXELFORMAT_INDEX4MSB"; 38 | case SDL_PIXELFORMAT_INDEX8: 39 | return "SDL_PIXELFORMAT_INDEX8"; 40 | case SDL_PIXELFORMAT_RGB332: 41 | return "SDL_PIXELFORMAT_RGB332"; 42 | case SDL_PIXELFORMAT_RGB444: 43 | return "SDL_PIXELFORMAT_RGB444"; 44 | case SDL_PIXELFORMAT_RGB555: 45 | return "SDL_PIXELFORMAT_RGB555"; 46 | case SDL_PIXELFORMAT_BGR555: 47 | return "SDL_PIXELFORMAT_BGR555"; 48 | case SDL_PIXELFORMAT_ARGB4444: 49 | return "SDL_PIXELFORMAT_ARGB4444"; 50 | case SDL_PIXELFORMAT_RGBA4444: 51 | return "SDL_PIXELFORMAT_RGBA4444"; 52 | case SDL_PIXELFORMAT_ABGR4444: 53 | return "SDL_PIXELFORMAT_ABGR4444"; 54 | case SDL_PIXELFORMAT_BGRA4444: 55 | return "SDL_PIXELFORMAT_BGRA4444"; 56 | case SDL_PIXELFORMAT_ARGB1555: 57 | return "SDL_PIXELFORMAT_ARGB1555"; 58 | case SDL_PIXELFORMAT_RGBA5551: 59 | return "SDL_PIXELFORMAT_RGBA5551"; 60 | case SDL_PIXELFORMAT_ABGR1555: 61 | return "SDL_PIXELFORMAT_ABGR1555"; 62 | case SDL_PIXELFORMAT_BGRA5551: 63 | return "SDL_PIXELFORMAT_BGRA5551"; 64 | case SDL_PIXELFORMAT_RGB565: 65 | return "SDL_PIXELFORMAT_RGB565"; 66 | case SDL_PIXELFORMAT_BGR565: 67 | return "SDL_PIXELFORMAT_BGR565"; 68 | case SDL_PIXELFORMAT_RGB24: 69 | return "SDL_PIXELFORMAT_RGB24"; 70 | case SDL_PIXELFORMAT_BGR24: 71 | return "SDL_PIXELFORMAT_BGR24"; 72 | case SDL_PIXELFORMAT_RGB888: 73 | return "SDL_PIXELFORMAT_RGB888"; 74 | case SDL_PIXELFORMAT_RGBX8888: 75 | return "SDL_PIXELFORMAT_RGBX8888"; 76 | case SDL_PIXELFORMAT_BGR888: 77 | return "SDL_PIXELFORMAT_BGR888"; 78 | case SDL_PIXELFORMAT_BGRX8888: 79 | return "SDL_PIXELFORMAT_BGRX8888"; 80 | case SDL_PIXELFORMAT_ARGB8888: 81 | return "SDL_PIXELFORMAT_ARGB8888"; 82 | case SDL_PIXELFORMAT_RGBA8888: 83 | return "SDL_PIXELFORMAT_RGBA8888"; 84 | case SDL_PIXELFORMAT_ABGR8888: 85 | return "SDL_PIXELFORMAT_ABGR8888"; 86 | case SDL_PIXELFORMAT_BGRA8888: 87 | return "SDL_PIXELFORMAT_BGRA8888"; 88 | case SDL_PIXELFORMAT_ARGB2101010: 89 | return "SDL_PIXELFORMAT_ARGB2101010"; 90 | case SDL_PIXELFORMAT_YV12: 91 | return "SDL_PIXELFORMAT_YV12"; 92 | case SDL_PIXELFORMAT_IYUV: 93 | return "SDL_PIXELFORMAT_IYUV"; 94 | case SDL_PIXELFORMAT_YUY2: 95 | return "SDL_PIXELFORMAT_YUY2"; 96 | case SDL_PIXELFORMAT_UYVY: 97 | return "SDL_PIXELFORMAT_UYVY"; 98 | case SDL_PIXELFORMAT_YVYU: 99 | return "SDL_PIXELFORMAT_YVYU"; 100 | case SDL_PIXELFORMAT_NV12: 101 | return "SDL_PIXELFORMAT_NV12"; 102 | case SDL_PIXELFORMAT_NV21: 103 | return "SDL_PIXELFORMAT_NV21"; 104 | default: 105 | return NULL; 106 | } 107 | } 108 | 109 | const char *Kit_GetKitStreamTypeString(unsigned int type) { 110 | switch(type) { 111 | case KIT_STREAMTYPE_UNKNOWN: 112 | return "KIT_STREAMTYPE_UNKNOWN"; 113 | case KIT_STREAMTYPE_VIDEO: 114 | return "KIT_STREAMTYPE_VIDEO"; 115 | case KIT_STREAMTYPE_AUDIO: 116 | return "KIT_STREAMTYPE_AUDIO"; 117 | case KIT_STREAMTYPE_DATA: 118 | return "KIT_STREAMTYPE_DATA"; 119 | case KIT_STREAMTYPE_SUBTITLE: 120 | return "KIT_STREAMTYPE_SUBTITLE"; 121 | case KIT_STREAMTYPE_ATTACHMENT: 122 | return "KIT_STREAMTYPE_ATTACHMENT"; 123 | default: 124 | return NULL; 125 | } 126 | } 127 | 128 | const char *Kit_GetHardwareDecoderTypeString(unsigned int type) { 129 | switch(type) { 130 | case KIT_HWDEVICE_TYPE_NONE: 131 | return "NONE"; 132 | case KIT_HWDEVICE_TYPE_VDPAU: 133 | return "VDPAU"; 134 | case KIT_HWDEVICE_TYPE_CUDA: 135 | return "CUDA"; 136 | case KIT_HWDEVICE_TYPE_VAAPI: 137 | return "VAAPI"; 138 | case KIT_HWDEVICE_TYPE_DXVA2: 139 | return "DXVA2"; 140 | case KIT_HWDEVICE_TYPE_QSV: 141 | return "QSV"; 142 | case KIT_HWDEVICE_TYPE_VIDEOTOOLBOX: 143 | return "VIDEOTOOLBOX"; 144 | case KIT_HWDEVICE_TYPE_D3D11VA: 145 | return "D3D11VA"; 146 | case KIT_HWDEVICE_TYPE_DRM: 147 | return "DRM"; 148 | case KIT_HWDEVICE_TYPE_OPENCL: 149 | return "OPENCL"; 150 | case KIT_HWDEVICE_TYPE_MEDIACODEC: 151 | return "MEDIACODEC"; 152 | case KIT_HWDEVICE_TYPE_VULKAN: 153 | return "VULKAN"; 154 | default: 155 | return NULL; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /examples/example_rawdump.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * This is an example how one would read raw data from SDL_Kitchensink. 8 | * This could technically be extended to use vulkan or opengl or whatever as a backend. 9 | * 10 | * Note that in real life this example makes no sense, since we read frames and save them to disk 11 | * in sync, which is slow. Normally you would just use ffmpeg directly for this purpose. 12 | */ 13 | 14 | #define MAX_FILESIZE 256 15 | 16 | typedef struct __attribute__((__packed__)) tga_header { 17 | uint8_t id; 18 | uint8_t colormap; 19 | uint8_t type; 20 | uint8_t colormap_spec[5]; 21 | uint16_t origin_x; 22 | uint16_t origin_y; 23 | uint16_t width; 24 | uint16_t height; 25 | uint8_t depth; 26 | uint8_t descriptor; 27 | } tga_header; 28 | 29 | bool write_tga(const char *filename, const unsigned char *src, uint16_t w, uint16_t h) { 30 | FILE *fp = fopen(filename, "wb"); 31 | if(fp == NULL) { 32 | return false; 33 | } 34 | 35 | // TGA Header 36 | const tga_header header = { 37 | .id = 0, 38 | .colormap = 0, 39 | .type = 2, 40 | .colormap_spec = {0, 0, 0, 0, 0}, 41 | .origin_x = 0, 42 | .origin_y = 0, 43 | .width = w, 44 | .height = h, 45 | .depth = 24, 46 | .descriptor = 0, 47 | }; 48 | fwrite(&header, sizeof(tga_header), 1, fp); 49 | 50 | // Image data 51 | const unsigned char *d = 0; 52 | for(int y = h - 1; y >= 0; y--) { 53 | for(int x = 0; x < w; x++) { 54 | d = src + (y * w + x) * 3; 55 | fwrite(d + 2, 1, 1, fp); 56 | fwrite(d + 1, 1, 1, fp); 57 | fwrite(d + 0, 1, 1, fp); 58 | } 59 | } 60 | 61 | fclose(fp); 62 | return true; 63 | } 64 | 65 | int main(int argc, char *argv[]) { 66 | int err = 0; 67 | const char *filename = NULL; 68 | const char *output_dir = NULL; 69 | int frame_index = 0; 70 | char file_name[MAX_FILESIZE]; 71 | bool run = true; 72 | Kit_Source *src = NULL; 73 | Kit_Player *player = NULL; 74 | 75 | // Get filename to open 76 | if(argc != 3) { 77 | fprintf(stderr, "Usage: rawdump \n"); 78 | return 0; 79 | } 80 | filename = argv[1]; 81 | output_dir = argv[2]; 82 | 83 | // Initialize Kitchensink with hardware decode and libass support. 84 | err = Kit_Init(KIT_INIT_ASS | KIT_INIT_HW_DECODE); 85 | if(err != 0) { 86 | fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); 87 | return 1; 88 | } 89 | 90 | // Open up the sourcefile. This can be a local file, network url, ... 91 | src = Kit_CreateSourceFromUrl(filename); 92 | if(src == NULL) { 93 | fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); 94 | return 1; 95 | } 96 | 97 | // Always output RGB888 so that we can easily process it. 98 | Kit_VideoFormatRequest v_req; 99 | Kit_ResetVideoFormatRequest(&v_req); 100 | v_req.format = SDL_PIXELFORMAT_RGB24; 101 | 102 | // Create the player. Pick best video and subtitle streams, and set subtitle rendering resolution. 103 | player = Kit_CreatePlayer( 104 | src, 105 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO), 106 | -1, 107 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE), 108 | &v_req, 109 | NULL, 110 | 1280, 111 | 720 112 | ); 113 | if(player == NULL) { 114 | fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); 115 | return 1; 116 | } 117 | 118 | // Print some information 119 | Kit_PlayerInfo pinfo; 120 | Kit_GetPlayerInfo(player, &pinfo); 121 | 122 | // Make sure there is video in the file to play first. 123 | if(Kit_GetPlayerVideoStream(player) == -1) { 124 | fprintf(stderr, "File contains no video!\n"); 125 | return 1; 126 | } 127 | 128 | // Start playback 129 | Kit_PlayerPlay(player); 130 | 131 | while(run) { 132 | if(Kit_GetPlayerState(player) == KIT_STOPPED) { 133 | run = false; 134 | continue; 135 | } 136 | 137 | // Lock the video output and fetch the frame data pointers. Data contains the video data, and line_size contains 138 | // the raw byte sizes of the buffers. When reading RGBA data, 139 | unsigned char **frame_data; 140 | unsigned char **subtitle_data; 141 | int *frame_line_size; 142 | SDL_Rect *source_rects; 143 | SDL_Rect *target_rects; 144 | SDL_Rect area; 145 | if(Kit_LockPlayerVideoRawFrame(player, &frame_data, &frame_line_size, &area) == 0) { 146 | // Since we are rendering on top of the image frame, the screen size is the same as the frame size. 147 | Kit_SetPlayerScreenSize(player, area.w, area.h); 148 | 149 | // Use SDL_Surfaces for simple blitting. Since we know that the source data is RGB24 - as we told Kit 150 | // to convert it - we can just declare the data as RGB24 here. 151 | SDL_Surface *pic = SDL_CreateRGBSurfaceWithFormatFrom(frame_data[0], area.w, area.h, 24, frame_line_size[0], SDL_PIXELFORMAT_RGB24); 152 | 153 | // Fetch and render subtitles on top of the image frame 154 | int subtitle_frames = Kit_GetPlayerSubtitleRawFrames(player, &subtitle_data, &source_rects, &target_rects); 155 | if(subtitle_frames > 0) { 156 | for (int i = 0; i < subtitle_frames; i++) { 157 | SDL_Rect *s = &source_rects[i]; 158 | SDL_Rect *t = &target_rects[i]; 159 | SDL_Surface *frame = SDL_CreateRGBSurfaceWithFormatFrom( 160 | subtitle_data[i], s->w, s->h, 32, s->w * 4, SDL_PIXELFORMAT_RGBA32); 161 | SDL_BlitScaled(frame, s, pic, t); 162 | SDL_FreeSurface(frame); 163 | } 164 | } 165 | 166 | // Write out the frame as TGA 167 | snprintf(file_name, MAX_FILESIZE, "%sframe_%d.tga", output_dir, frame_index); 168 | write_tga(file_name, frame_data[0], area.w, area.h); 169 | fprintf(stderr, "Got frame %d: %d x %d, %d subtitle frames, saved to %s\n", frame_index, area.w, area.h, subtitle_frames, file_name); 170 | 171 | // We are done with data. Unlock the video output. This invalidates the data and line_size pointers! 172 | SDL_FreeSurface(pic); 173 | Kit_UnlockPlayerVideoRawFrame(player); 174 | frame_index++; 175 | } 176 | } 177 | 178 | Kit_ClosePlayer(player); 179 | Kit_CloseSource(src); 180 | Kit_Quit(); 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(SDL_kitchensink C) 3 | include(GNUInstallDirs) 4 | include(CheckLibraryExists) 5 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 6 | 7 | set(KIT_VERSION_MAJOR "2") 8 | set(KIT_VERSION_MINOR "0") 9 | set(KIT_VERSION_PATCH "0") 10 | set(KIT_VERSION ${KIT_VERSION_MAJOR}.${KIT_VERSION_MINOR}.${KIT_VERSION_PATCH}) 11 | add_definitions( 12 | -DKIT_VERSION_MAJOR=${KIT_VERSION_MAJOR} 13 | -DKIT_VERSION_MINOR=${KIT_VERSION_MINOR} 14 | -DKIT_VERSION_PATCH=${KIT_VERSION_PATCH} 15 | ) 16 | 17 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 18 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -ggdb -Werror -fno-omit-frame-pointer") 19 | set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -ggdb -O2 -fno-omit-frame-pointer -DNDEBUG") 20 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O2 -DNDEBUG") 21 | set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -Os -DNDEBUG") 22 | 23 | option(BUILD_EXAMPLES "Build examples" OFF) 24 | option(USE_DYNAMIC_LIBASS "Use dynamically loaded libass" OFF) 25 | option(USE_ASAN "Use AddressSanitizer" OFF) 26 | option(USE_TIDY "Use clang-tidy" OFF) 27 | option(BUILD_SHARED "Build shared library" ON) 28 | option(BUILD_STATIC "Build static library" ON) 29 | 30 | if (NOT BUILD_SHARED AND NOT BUILD_STATIC) 31 | message(FATAL_ERROR "Nothing to build, set BUILD_SHARED and/or BUILD_STATIC.") 32 | endif () 33 | 34 | if (BUILD_SHARED) 35 | message(STATUS "Building shared libraries") 36 | else () 37 | message(STATUS "NOT building shared libraries") 38 | endif () 39 | 40 | if (BUILD_STATIC) 41 | message(STATUS "Building static libraries") 42 | else () 43 | message(STATUS "NOT building static libraries") 44 | endif () 45 | 46 | if (USE_ASAN) 47 | message(STATUS "DEVELOPMENT: AddressSanitizer enabled!") 48 | endif () 49 | 50 | find_package(SDL2 REQUIRED) 51 | find_package(ffmpeg COMPONENTS avcodec avformat avutil swscale swresample) 52 | 53 | check_library_exists(m pow "" LIBM_EXISTS) 54 | 55 | set(LIBRARIES 56 | ${SDL2_LIBRARIES} 57 | ${FFMPEG_LIBRARIES} 58 | ) 59 | set(INCLUDES 60 | include/ 61 | ${SDL2_INCLUDE_DIRS} 62 | ${FFMPEG_INCLUDE_DIRS} 63 | ) 64 | 65 | if (LIBM_EXISTS) 66 | list(APPEND LIBRARIES "m") 67 | endif () 68 | 69 | if (USE_DYNAMIC_LIBASS) 70 | if (WIN32 OR MINGW OR MSYS) 71 | set(DYNAMIC_LIBASS_NAME "\"libass-9.dll\"") 72 | else () 73 | set(DYNAMIC_LIBASS_NAME "\"libass.so\"") 74 | endif () 75 | add_definitions(-DUSE_DYNAMIC_LIBASS) 76 | add_definitions(-DDYNAMIC_LIBASS_NAME=${DYNAMIC_LIBASS_NAME}) 77 | else () 78 | find_package(ass) 79 | set(LIBRARIES ${LIBRARIES} ${ASS_LIBRARIES}) 80 | set(INCLUDES ${INCLUDES} ${ASS_INCLUDE_DIRS}) 81 | endif () 82 | 83 | FILE(GLOB_RECURSE SOURCES "src/*.c") 84 | FILE(GLOB INSTALL_HEADERS "include/kitchensink2/*.h") 85 | 86 | include_directories(${INCLUDES}) 87 | 88 | set(INSTALL_TARGETS "") 89 | 90 | if (BUILD_SHARED) 91 | add_library(SDL_kitchensink SHARED ${SOURCES}) 92 | 93 | if (USE_ASAN) 94 | target_compile_options(SDL_kitchensink PRIVATE "-fsanitize=address") 95 | target_link_libraries(SDL_kitchensink asan) 96 | endif () 97 | 98 | target_link_libraries(SDL_kitchensink ${LIBRARIES}) 99 | 100 | set_target_properties(SDL_kitchensink PROPERTIES VERSION ${KIT_VERSION}) 101 | set_target_properties(SDL_kitchensink PROPERTIES SOVERSION ${KIT_VERSION_MAJOR}) 102 | set_target_properties(SDL_kitchensink PROPERTIES DEBUG_POSTFIX "d") 103 | 104 | target_compile_definitions(SDL_kitchensink PRIVATE "KIT_DLL;KIT_DLL_EXPORTS") 105 | target_compile_options(SDL_kitchensink PRIVATE "-fvisibility=hidden") 106 | 107 | set_property(TARGET SDL_kitchensink PROPERTY C_STANDARD 99) 108 | 109 | set(INSTALL_TARGETS SDL_kitchensink ${INSTALL_TARGETS}) 110 | endif () 111 | 112 | if (BUILD_STATIC) 113 | add_library(SDL_kitchensink_static STATIC ${SOURCES}) 114 | 115 | if (USE_ASAN) 116 | target_compile_options(SDL_kitchensink_static PRIVATE "-fsanitize=address") 117 | endif () 118 | 119 | set_target_properties(SDL_kitchensink_static PROPERTIES DEBUG_POSTFIX "d") 120 | set_property(TARGET SDL_kitchensink_static PROPERTY C_STANDARD 99) 121 | 122 | set(INSTALL_TARGETS SDL_kitchensink_static ${INSTALL_TARGETS}) 123 | endif () 124 | 125 | if (USE_TIDY) 126 | if (BUILD_STATIC) 127 | set_target_properties(SDL_kitchensink_static PROPERTIES C_CLANG_TIDY "clang-tidy") 128 | else () 129 | set_target_properties(SDL_kitchensink PROPERTIES C_CLANG_TIDY "clang-tidy") 130 | endif () 131 | message(STATUS "Development: clang-tidy enabled") 132 | else () 133 | message(STATUS "Development: clang-tidy disabled") 134 | endif () 135 | 136 | set(PKG_CONFIG_FILE "${CMAKE_CURRENT_BINARY_DIR}/SDL_kitchensink.pc") 137 | configure_file( 138 | "${CMAKE_CURRENT_SOURCE_DIR}/pkg-config.pc.in" 139 | ${PKG_CONFIG_FILE} 140 | @ONLY 141 | ) 142 | 143 | if (BUILD_EXAMPLES) 144 | list(APPEND EXAMPLE_TARGETS audio complex simple custom rwops rawdump) 145 | 146 | # If we are building static, just link all libraries (ffmpeg, sdl, etc.) 147 | # If building shared, link shared kitchensink + SDL2 (ffmpeg gets pulled by kitchensink) 148 | if (BUILD_STATIC) 149 | set(EXAMPLE_LIBRARIES SDL_kitchensink_static ${LIBRARIES}) 150 | else () 151 | set(EXAMPLE_LIBRARIES SDL_kitchensink ${SDL2_LIBRARIES}) 152 | endif () 153 | if (USE_ASAN) 154 | set(EXAMPLE_LIBRARIES asan ${EXAMPLE_LIBRARIES}) 155 | endif () 156 | 157 | foreach (TARGET ${EXAMPLE_TARGETS}) 158 | add_executable(${TARGET} examples/example_${TARGET}.c) 159 | set_property(TARGET ${TARGET} PROPERTY C_STANDARD 99) 160 | target_link_libraries(${TARGET} ${EXAMPLE_LIBRARIES}) 161 | 162 | if (MINGW) 163 | # If we are compiling with mingw, remember to link in mingw libs and set console mode 164 | # This way stdout/stderr are handled correctly. 165 | target_link_libraries(${TARGET} mingw32) 166 | set_target_properties(${TARGET} PROPERTIES LINK_FLAGS "-mconsole") 167 | endif () 168 | 169 | if (USE_ASAN) 170 | target_compile_options(${TARGET} PRIVATE "-fsanitize=address") 171 | endif () 172 | endforeach () 173 | endif () 174 | 175 | # documentation target 176 | add_custom_target(docs COMMAND doxygen WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) 177 | 178 | # Installation 179 | INSTALL(FILES ${PKG_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 180 | INSTALL(FILES ${INSTALL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/kitchensink2) 181 | INSTALL(TARGETS ${INSTALL_TARGETS} 182 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 183 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 184 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 185 | ) 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SDL_kitchensink 2 | 3 | [![CI](https://github.com/katajakasa/SDL_kitchensink/actions/workflows/ci.yml/badge.svg)](https://github.com/katajakasa/SDL_kitchensink/actions/workflows/ci.yml) 4 | 5 | FFmpeg and SDL2 based library for audio and video playback, written in C99. 6 | 7 | Documentation is available at http://katajakasa.github.io/SDL_kitchensink/ 8 | 9 | Features: 10 | 11 | * Decoding video, audio and subtitles via FFmpeg 12 | * Dumping video and subtitle data on SDL_Textures or software surfaces 13 | * Dumping audio data in the usual mono/stereo interleaved formats 14 | * Automatic audio and video conversion to SDL2 friendly formats 15 | * Synchronizing video & audio to clock 16 | * Stream seeking 17 | * Bitmap, text and SSA/ASS subtitle support 18 | * Video hardware decoding (optionally) 19 | 20 | Note! Master branch is for the development of v2.x.x series. 21 | 22 | * v1 can be found in the release/v1 branch. Only smaller bugfixes will be accepted / added. 23 | * v0 is no longer in development, and no fixes of any kind will be made or accepted. 24 | 25 | | Version | Supported | Bugfixes | New features | Branch | 26 | |---------|--------------------|--------------------|--------------------|------------| 27 | | 2.x.x | :white_check_mark: | :white_check_mark: | :white_check_mark: | master | 28 | | 1.x.x | :white_check_mark: | :white_check_mark: | :x: | release/v1 | 29 | | 0.x.x | :x: | :x: | :x: | release/v0 | 30 | 31 | ## 1. Installation 32 | 33 | Nowadays you can find SDL_kitchensink in eg. linux repositories. Installation might be as simple as 34 | running the following (or your distributions' equivalent): 35 | 36 | ```apt install libsdl-kitchensink libsdl-kitchensink-dev``` 37 | 38 | If you are running on windows/MSYS2 or on linux distributions where the package management does not 39 | have kitchensink, you will need to compile it yourself. Please see the "Compiling" section below. 40 | 41 | ## 2. Library requirements 42 | 43 | Build requirements: 44 | 45 | * CMake (>=3.10) 46 | * GCC (C99 support required) 47 | 48 | Library requirements: 49 | 50 | * SDL2 2.0.5 or newer 51 | * FFmpeg 5.0 or newer 52 | * libass (optional, supports runtime linking via SDL_LoadSO) 53 | 54 | Note that Clang might work, but is not tested. Older SDL2 and FFmpeg library versions 55 | may or may not work; versions noted here are the only ones tested. 56 | 57 | ### 2.1. Debian / Ubuntu 58 | 59 | ``` 60 | sudo apt-get install libsdl2-dev libavcodec-dev libavformat-dev \ 61 | libavutil-dev libswresample-dev libswscale-dev libass-dev 62 | ``` 63 | 64 | ### 2.2. MSYS2 64bit 65 | 66 | These are for x86_64. For 32bit installation, just change the package names a bit . 67 | 68 | ``` 69 | pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libass 70 | ``` 71 | 72 | ## 3. Compiling 73 | 74 | By default, both static and dynamic libraries are built. 75 | 76 | * Set BUILD_STATIC off if you don't want to build static library 77 | * Set BUILD_SHARED off if you don't want to build shared library 78 | * Dynamic library is called libSDL_kitchensink.dll or .so 79 | * Static library is called libSDL_kitchensink_static.a 80 | * If you build in debug mode (```-DCMAKE_BUILD_TYPE=Debug```), libraries will be postfixed with 'd'. 81 | 82 | Change CMAKE_INSTALL_PREFIX as necessary to change the installation path. The files will be installed to 83 | 84 | * CMAKE_INSTALL_PREFIX/lib for libraries (.dll.a, .a, etc.) 85 | * CMAKE_INSTALL_PREFIX/bin for binaries (.dll, .so) 86 | * CMAKE_INSTALL_PREFIX/include for headers 87 | 88 | ### 3.1. Building the libraries on Debian/Ubuntu 89 | 90 | 1. ```mkdir build && cd build``` 91 | 2. ```cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..``` 92 | 3. ```make -j``` 93 | 4. ```sudo make install``` 94 | 95 | ### 3.2. Building the libraries on MSYS2 96 | 97 | 1. ```mkdir build && cd build``` 98 | 2. ```cmake -G "MSYS Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..``` 99 | 3. ```make``` 100 | 4. ```make install``` 101 | 102 | ### 3.3. Building examples 103 | 104 | Just add ```-DBUILD_EXAMPLES=1``` to cmake arguments and rebuild. 105 | 106 | ### 3.4. Building with AddressSanitizer 107 | 108 | This is for development/debugging use only! 109 | 110 | Make sure llvm is installed, then add ```-DUSE_ASAN=1``` to the cmake arguments and rebuild. Note that ASAN is not 111 | supported on all OSes (eg. windows). 112 | 113 | After building, you can run with the following (make sure to set correct llvm-symbolizer path): 114 | 115 | ``` 116 | ASAN_OPTIONS=symbolize=1 ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer ./complex 117 | ``` 118 | 119 | ## 4. Q&A 120 | 121 | Q: What's with the USE_DYNAMIC_LIBASS cmake flag ? 122 | 123 | * A: It can be used to link the libass dynamically when needed. This also makes it possible to build the 124 | library without libass, if needed. Using this flag is not recommended however, and it will probably 125 | be deprecated in the next major version(s). If you use it, you might need to also patch the library 126 | path and name to match yours in kitchensink source. 127 | 128 | Q: Why the name SDL_kitchensink 129 | 130 | * A: Because pulling major blob of library code like ffmpeg feels like bringing in a whole house with its 131 | kitchensink and everything to the project. Also, it sounded funny. Also, SDL_ffmpeg is already reserved :( 132 | 133 | ## 5. Examples 134 | 135 | Please see examples directory. You can also take a look at unittests for some help. 136 | Note that examples are NOT meant for any kind of real life use; they are only meant to 137 | show simple use cases for the library. 138 | 139 | ## 6. FFMPEG & licensing 140 | 141 | Note that FFmpeg has a rather complex license. Please take a look at 142 | [FFmpeg Legal page](http://ffmpeg.org/legal.html) for details. 143 | 144 | ## 7. License 145 | 146 | ``` 147 | The MIT License (MIT) 148 | 149 | Copyright (c) 2020 Tuomas Virtanen 150 | 151 | Permission is hereby granted, free of charge, to any person obtaining a copy 152 | of this software and associated documentation files (the "Software"), to deal 153 | in the Software without restriction, including without limitation the rights 154 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 155 | copies of the Software, and to permit persons to whom the Software is 156 | furnished to do so, subject to the following conditions: 157 | 158 | The above copyright notice and this permission notice shall be included in all 159 | copies or substantial portions of the Software. 160 | 161 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 162 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 163 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 164 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 165 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 166 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 167 | SOFTWARE. 168 | ``` 169 | -------------------------------------------------------------------------------- /src/internal/kitdemuxer.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "kitchensink2/internal/kitdemuxer.h" 7 | #include "kitchensink2/internal/kitlibstate.h" 8 | #include "kitchensink2/internal/kitpacketbuffer.h" 9 | #include "kitchensink2/kiterror.h" 10 | 11 | static void Kit_SendEOFPacket(Kit_Demuxer *demuxer) { 12 | for(int i = 0; i < KIT_INDEX_COUNT; i++) { 13 | if(!demuxer->buffers[i]) 14 | continue; 15 | demuxer->scratch_packet->opaque = (void *)2; 16 | Kit_WritePacketBuffer(demuxer->buffers[i], demuxer->scratch_packet); 17 | } 18 | } 19 | 20 | bool Kit_RunDemuxer(Kit_Demuxer *demuxer) { 21 | if(av_read_frame(demuxer->src->format_ctx, demuxer->scratch_packet) < 0) { 22 | Kit_SendEOFPacket(demuxer); 23 | return false; 24 | } 25 | 26 | // Figure out if we are interested in this stream. If we are, write the packet to a buffer for decoder to pick up. 27 | // Note that Kit_WritePacketBuffer() may block if the buffer is full. It will also move the scratch_packet 28 | // references to its own buffer, leaving the scratch_buffer in a clean state. 29 | for(int i = 0; i < KIT_INDEX_COUNT; i++) { 30 | if(demuxer->scratch_packet->stream_index == demuxer->stream_indexes[i]) { 31 | Kit_WritePacketBuffer(demuxer->buffers[i], demuxer->scratch_packet); 32 | return true; 33 | } 34 | } 35 | 36 | // Packet does not belong to any stream we are interested in, so get rid of it. 37 | av_packet_unref(demuxer->scratch_packet); 38 | return true; 39 | } 40 | 41 | Kit_Demuxer *Kit_CreateDemuxer(const Kit_Source *src, int video_index, int audio_index, int subtitle_index) { 42 | Kit_LibraryState *state = Kit_GetLibraryState(); 43 | Kit_Demuxer *demuxer = NULL; 44 | Kit_PacketBuffer *video_buf = NULL; 45 | Kit_PacketBuffer *audio_buf = NULL; 46 | Kit_PacketBuffer *subtitle_buf = NULL; 47 | AVPacket *scratch_packet; 48 | 49 | if((demuxer = calloc(1, sizeof(Kit_Demuxer))) == NULL) { 50 | Kit_SetError("Unable to allocate demuxer"); 51 | goto error_0; 52 | } 53 | if((scratch_packet = av_packet_alloc()) == NULL) { 54 | goto error_1; 55 | } 56 | if(video_index >= 0) { 57 | video_buf = Kit_CreatePacketBuffer( 58 | state->video_packet_buffer_size, 59 | (buf_obj_alloc)av_packet_alloc, 60 | (buf_obj_unref)av_packet_unref, 61 | (buf_obj_free)av_packet_free, 62 | (buf_obj_move)av_packet_move_ref, 63 | (buf_obj_ref)av_packet_ref 64 | ); 65 | if(video_buf == NULL) { 66 | Kit_SetError("Unable to allocate video packet buffer"); 67 | goto error_2; 68 | } 69 | } 70 | if(audio_index >= 0) { 71 | audio_buf = Kit_CreatePacketBuffer( 72 | state->audio_packet_buffer_size, 73 | (buf_obj_alloc)av_packet_alloc, 74 | (buf_obj_unref)av_packet_unref, 75 | (buf_obj_free)av_packet_free, 76 | (buf_obj_move)av_packet_move_ref, 77 | (buf_obj_ref)av_packet_ref 78 | ); 79 | if(audio_buf == NULL) { 80 | Kit_SetError("Unable to allocate audio packet buffer"); 81 | goto error_3; 82 | } 83 | } 84 | if(subtitle_index >= 0) { 85 | subtitle_buf = Kit_CreatePacketBuffer( 86 | state->subtitle_packet_buffer_size, 87 | (buf_obj_alloc)av_packet_alloc, 88 | (buf_obj_unref)av_packet_unref, 89 | (buf_obj_free)av_packet_free, 90 | (buf_obj_move)av_packet_move_ref, 91 | (buf_obj_ref)av_packet_ref 92 | ); 93 | if(subtitle_buf == NULL) { 94 | Kit_SetError("Unable to allocate subtitle packet buffer"); 95 | goto error_4; 96 | } 97 | } 98 | 99 | demuxer->src = src; 100 | demuxer->scratch_packet = scratch_packet; 101 | demuxer->buffers[KIT_VIDEO_INDEX] = video_buf; 102 | demuxer->buffers[KIT_AUDIO_INDEX] = audio_buf; 103 | demuxer->buffers[KIT_SUBTITLE_INDEX] = subtitle_buf; 104 | demuxer->stream_indexes[KIT_VIDEO_INDEX] = video_index; 105 | demuxer->stream_indexes[KIT_AUDIO_INDEX] = audio_index; 106 | demuxer->stream_indexes[KIT_SUBTITLE_INDEX] = subtitle_index; 107 | return demuxer; 108 | 109 | error_4: 110 | Kit_FreePacketBuffer(&audio_buf); 111 | error_3: 112 | Kit_FreePacketBuffer(&video_buf); 113 | error_2: 114 | av_packet_free(&scratch_packet); 115 | error_1: 116 | free(demuxer); 117 | error_0: 118 | return NULL; 119 | } 120 | 121 | void Kit_ClearDemuxerBuffers(const Kit_Demuxer *demuxer) { 122 | if(!demuxer) 123 | return; 124 | for(int i = 0; i < KIT_INDEX_COUNT; i++) 125 | Kit_FlushPacketBuffer(demuxer->buffers[i]); 126 | } 127 | 128 | void Kit_SetDemuxerStreamIndex(Kit_Demuxer *demuxer, Kit_BufferIndex index, int stream_index) { 129 | Kit_FlushPacketBuffer(demuxer->buffers[index]); 130 | demuxer->stream_indexes[index] = stream_index; 131 | } 132 | 133 | void Kit_SignalDemuxer(const Kit_Demuxer *demuxer) { 134 | if(!demuxer) 135 | return; 136 | for(int i = 0; i < KIT_INDEX_COUNT; i++) 137 | Kit_SignalPacketBuffer(demuxer->buffers[i]); 138 | } 139 | 140 | Kit_PacketBuffer *Kit_GetDemuxerPacketBuffer(const Kit_Demuxer *demuxer, Kit_BufferIndex buffer_index) { 141 | assert(demuxer); 142 | return demuxer->buffers[buffer_index]; 143 | } 144 | 145 | void Kit_CloseDemuxer(Kit_Demuxer **ref) { 146 | if(!ref || !*ref) 147 | return; 148 | 149 | Kit_Demuxer *demuxer = *ref; 150 | for(int i = 0; i < KIT_INDEX_COUNT; i++) { 151 | Kit_FreePacketBuffer(&demuxer->buffers[i]); 152 | demuxer->stream_indexes[i] = -1; 153 | } 154 | av_packet_free(&demuxer->scratch_packet); 155 | free(demuxer); 156 | *ref = NULL; 157 | } 158 | 159 | static void Kit_SendSeekPacket(Kit_Demuxer *demuxer) { 160 | for(int i = 0; i < KIT_INDEX_COUNT; i++) { 161 | if(!demuxer->buffers[i]) 162 | continue; 163 | demuxer->scratch_packet->opaque = (void *)1; 164 | Kit_FlushPacketBuffer(demuxer->buffers[i]); 165 | Kit_WritePacketBuffer(demuxer->buffers[i], demuxer->scratch_packet); 166 | } 167 | } 168 | 169 | bool Kit_DemuxerSeek(Kit_Demuxer *demuxer, int64_t seek_target) { 170 | if(avformat_seek_file(demuxer->src->format_ctx, -1, INT64_MIN, seek_target, INT64_MAX, 0) >= 0) { 171 | Kit_ClearDemuxerBuffers(demuxer); 172 | Kit_SendSeekPacket(demuxer); 173 | return true; 174 | } 175 | return false; 176 | } 177 | 178 | void Kit_GetDemuxerBufferState( 179 | const Kit_Demuxer *demuxer, Kit_BufferIndex buffer_index, unsigned int *length, unsigned int *capacity 180 | ) { 181 | Kit_PacketBuffer *buffer; 182 | if(!demuxer || !(buffer = demuxer->buffers[buffer_index])) 183 | return; 184 | if(length != NULL) 185 | *length = Kit_GetPacketBufferLength(buffer); 186 | if(capacity != NULL) 187 | *capacity = Kit_GetPacketBufferCapacity(buffer); 188 | } -------------------------------------------------------------------------------- /examples/example_simple.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * Note! This example does not do proper error handling etc. 8 | * It is for example use only! 9 | */ 10 | 11 | #define AUDIO_BUFFER_SIZE (1024 * 64) 12 | #define ATLAS_WIDTH 4096 13 | #define ATLAS_HEIGHT 4096 14 | #define ATLAS_MAX 1024 15 | 16 | int main(int argc, char *argv[]) { 17 | int err = 0, ret = 0; 18 | const char *filename = NULL; 19 | SDL_Window *window = NULL; 20 | SDL_Renderer *renderer = NULL; 21 | bool run = true; 22 | Kit_Source *src = NULL; 23 | Kit_Player *player = NULL; 24 | SDL_AudioSpec wanted_spec, audio_spec; 25 | SDL_AudioDeviceID audio_dev; 26 | 27 | // Get filename to open 28 | if(argc != 2) { 29 | fprintf(stderr, "Usage: simple \n"); 30 | return 0; 31 | } 32 | filename = argv[1]; 33 | 34 | // Init SDL 35 | err = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); 36 | if(err != 0) { 37 | fprintf(stderr, "Unable to initialize SDL2!\n"); 38 | return 1; 39 | } 40 | 41 | // Create a resizable window. 42 | window = 43 | SDL_CreateWindow(filename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE); 44 | if(window == NULL) { 45 | fprintf(stderr, "Unable to create a new window!\n"); 46 | return 1; 47 | } 48 | 49 | // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay. 50 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 51 | if(renderer == NULL) { 52 | fprintf(stderr, "Unable to create a renderer!\n"); 53 | return 1; 54 | } 55 | 56 | // Initialize Kitchensink with network and libass support. 57 | err = Kit_Init(KIT_INIT_NETWORK | KIT_INIT_ASS); 58 | if(err != 0) { 59 | fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); 60 | return 1; 61 | } 62 | 63 | // Open up the sourcefile. 64 | // This can be a local file, network url, ... 65 | src = Kit_CreateSourceFromUrl(filename); 66 | if(src == NULL) { 67 | fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); 68 | return 1; 69 | } 70 | 71 | // Create the player. Pick best video, audio and subtitle streams, and set subtitle 72 | // rendering resolution to screen resolution. 73 | player = Kit_CreatePlayer( 74 | src, 75 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO), 76 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO), 77 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE), 78 | NULL, 79 | NULL, 80 | 1280, 81 | 720 82 | ); 83 | if(player == NULL) { 84 | fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); 85 | return 1; 86 | } 87 | 88 | // Print some information 89 | Kit_PlayerInfo pinfo; 90 | Kit_GetPlayerInfo(player, &pinfo); 91 | 92 | // Make sure there is video in the file to play first. 93 | if(Kit_GetPlayerVideoStream(player) == -1) { 94 | fprintf(stderr, "File contains no video!\n"); 95 | return 1; 96 | } 97 | 98 | // Init audio 99 | SDL_memset(&wanted_spec, 0, sizeof(wanted_spec)); 100 | wanted_spec.freq = pinfo.audio_format.sample_rate; 101 | wanted_spec.format = pinfo.audio_format.format; 102 | wanted_spec.channels = pinfo.audio_format.channels; 103 | audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0); 104 | SDL_PauseAudioDevice(audio_dev, 0); 105 | 106 | // Initialize video texture. This will probably end up as YV12 most of the time. 107 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); 108 | SDL_Texture *video_tex = SDL_CreateTexture( 109 | renderer, 110 | pinfo.video_format.format, 111 | SDL_TEXTUREACCESS_STATIC, 112 | pinfo.video_format.width, 113 | pinfo.video_format.height 114 | ); 115 | if(video_tex == NULL) { 116 | fprintf(stderr, "Error while attempting to create a video texture\n"); 117 | return 1; 118 | } 119 | 120 | // This is the subtitle texture atlas. This contains all the subtitle image fragments. 121 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations 122 | SDL_Texture *subtitle_tex = 123 | SDL_CreateTexture(renderer, pinfo.subtitle_format.format, SDL_TEXTUREACCESS_STATIC, ATLAS_WIDTH, ATLAS_HEIGHT); 124 | if(subtitle_tex == NULL) { 125 | fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n"); 126 | return 1; 127 | } 128 | 129 | // Make sure subtitle texture is in correct blending mode 130 | SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND); 131 | 132 | // Clear screen with black 133 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 134 | SDL_RenderClear(renderer); 135 | 136 | // Start playback 137 | Kit_PlayerPlay(player); 138 | 139 | // Playback temporary data buffers 140 | char audio_buf[AUDIO_BUFFER_SIZE]; 141 | SDL_Rect sources[ATLAS_MAX]; 142 | SDL_Rect targets[ATLAS_MAX]; 143 | 144 | // Get movie area size 145 | SDL_RenderSetLogicalSize(renderer, pinfo.video_format.width, pinfo.video_format.height); 146 | while(run) { 147 | if(Kit_GetPlayerState(player) == KIT_STOPPED) { 148 | run = false; 149 | continue; 150 | } 151 | 152 | SDL_Event event; 153 | while(SDL_PollEvent(&event)) { 154 | switch(event.type) { 155 | case SDL_QUIT: 156 | run = false; 157 | break; 158 | } 159 | } 160 | 161 | // Refresh audio 162 | int queued = SDL_GetQueuedAudioSize(audio_dev); 163 | if(queued < AUDIO_BUFFER_SIZE) { 164 | int need = AUDIO_BUFFER_SIZE - queued; 165 | 166 | while(need > 0) { 167 | ret = Kit_GetPlayerAudioData(player, queued, (unsigned char *)audio_buf, AUDIO_BUFFER_SIZE); 168 | need -= ret; 169 | if(ret > 0) { 170 | SDL_QueueAudio(audio_dev, audio_buf, ret); 171 | } else { 172 | break; 173 | } 174 | } 175 | // If we now have data, start playback (again) 176 | if(SDL_GetQueuedAudioSize(audio_dev) > 0) { 177 | SDL_PauseAudioDevice(audio_dev, 0); 178 | } 179 | } 180 | 181 | // Refresh video texture and render it 182 | Kit_GetPlayerVideoSDLTexture(player, video_tex, NULL); 183 | SDL_RenderCopy(renderer, video_tex, NULL, NULL); 184 | 185 | // Refresh subtitle texture atlas and render subtitle frames from it 186 | // For subtitles, use screen size instead of video size for best quality 187 | int got = Kit_GetPlayerSubtitleSDLTexture(player, subtitle_tex, sources, targets, ATLAS_MAX); 188 | for(int i = 0; i < got; i++) { 189 | SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]); 190 | } 191 | 192 | // Render to screen + wait for vsync 193 | SDL_RenderPresent(renderer); 194 | } 195 | 196 | Kit_ClosePlayer(player); 197 | Kit_CloseSource(src); 198 | Kit_Quit(); 199 | 200 | SDL_DestroyTexture(subtitle_tex); 201 | SDL_DestroyTexture(video_tex); 202 | SDL_CloseAudioDevice(audio_dev); 203 | 204 | SDL_DestroyRenderer(renderer); 205 | SDL_DestroyWindow(window); 206 | SDL_Quit(); 207 | return 0; 208 | } 209 | -------------------------------------------------------------------------------- /examples/example_rwops.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * Note! This example does not do proper error handling etc. 8 | * It is for example use only! 9 | */ 10 | 11 | #define AUDIO_BUFFER_SIZE (1024 * 64) 12 | #define ATLAS_WIDTH 4096 13 | #define ATLAS_HEIGHT 4096 14 | #define ATLAS_MAX 1024 15 | 16 | int main(int argc, char *argv[]) { 17 | int err = 0, ret = 0; 18 | const char *filename = NULL; 19 | SDL_Window *window = NULL; 20 | SDL_Renderer *renderer = NULL; 21 | bool run = true; 22 | Kit_Source *src = NULL; 23 | Kit_Player *player = NULL; 24 | SDL_AudioSpec wanted_spec, audio_spec; 25 | SDL_AudioDeviceID audio_dev; 26 | 27 | // Get filename to open 28 | if(argc != 2) { 29 | fprintf(stderr, "Usage: rwops \n"); 30 | return 0; 31 | } 32 | filename = argv[1]; 33 | 34 | // Init SDL 35 | err = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); 36 | if(err != 0) { 37 | fprintf(stderr, "Unable to initialize SDL2!\n"); 38 | return 1; 39 | } 40 | 41 | // Create a resizable window. 42 | window = 43 | SDL_CreateWindow(filename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE); 44 | if(window == NULL) { 45 | fprintf(stderr, "Unable to create a new window!\n"); 46 | return 1; 47 | } 48 | 49 | // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay. 50 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 51 | if(renderer == NULL) { 52 | fprintf(stderr, "Unable to create a renderer!\n"); 53 | return 1; 54 | } 55 | 56 | // Initialize Kitchensink with network and libass support. 57 | err = Kit_Init(KIT_INIT_NETWORK | KIT_INIT_ASS); 58 | if(err != 0) { 59 | fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); 60 | return 1; 61 | } 62 | 63 | // Open file with fopen. We then proceed to read this with our custom file handlers. 64 | SDL_RWops *rw_ops = SDL_RWFromFile(filename, "rb"); 65 | if(rw_ops == NULL) { 66 | fprintf(stderr, "Unable to open file '%s' for reading\n", filename); 67 | return 1; 68 | } 69 | 70 | // Open up the SDL RWops source 71 | src = Kit_CreateSourceFromRW(rw_ops); 72 | if(src == NULL) { 73 | fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); 74 | return 1; 75 | } 76 | 77 | // Create the player. Pick best video, audio and subtitle streams, and set subtitle 78 | // rendering resolution to screen resolution. 79 | player = Kit_CreatePlayer( 80 | src, 81 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO), 82 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO), 83 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE), 84 | NULL, 85 | NULL, 86 | 1280, 87 | 720 88 | ); 89 | if(player == NULL) { 90 | fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); 91 | return 1; 92 | } 93 | 94 | // Print some information 95 | Kit_PlayerInfo pinfo; 96 | Kit_GetPlayerInfo(player, &pinfo); 97 | 98 | // Make sure there is video in the file to play first. 99 | if(Kit_GetPlayerVideoStream(player) == -1) { 100 | fprintf(stderr, "File contains no video!\n"); 101 | return 1; 102 | } 103 | 104 | // Init audio 105 | SDL_memset(&wanted_spec, 0, sizeof(wanted_spec)); 106 | wanted_spec.freq = pinfo.audio_format.sample_rate; 107 | wanted_spec.format = pinfo.audio_format.format; 108 | wanted_spec.channels = pinfo.audio_format.channels; 109 | audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0); 110 | SDL_PauseAudioDevice(audio_dev, 0); 111 | 112 | // Initialize video texture. This will probably end up as YV12 most of the time. 113 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); 114 | SDL_Texture *video_tex = SDL_CreateTexture( 115 | renderer, 116 | pinfo.video_format.format, 117 | SDL_TEXTUREACCESS_STATIC, 118 | pinfo.video_format.width, 119 | pinfo.video_format.height 120 | ); 121 | if(video_tex == NULL) { 122 | fprintf(stderr, "Error while attempting to create a video texture\n"); 123 | return 1; 124 | } 125 | 126 | // This is the subtitle texture atlas. This contains all the subtitle image fragments. 127 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations 128 | SDL_Texture *subtitle_tex = 129 | SDL_CreateTexture(renderer, pinfo.subtitle_format.format, SDL_TEXTUREACCESS_STATIC, ATLAS_WIDTH, ATLAS_HEIGHT); 130 | if(subtitle_tex == NULL) { 131 | fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n"); 132 | return 1; 133 | } 134 | 135 | // Make sure subtitle texture is in correct blending mode 136 | SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND); 137 | 138 | // Clear screen with black 139 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 140 | SDL_RenderClear(renderer); 141 | 142 | // Start playback 143 | Kit_PlayerPlay(player); 144 | 145 | // Playback temporary data buffers 146 | char audio_buf[AUDIO_BUFFER_SIZE]; 147 | SDL_Rect sources[ATLAS_MAX]; 148 | SDL_Rect targets[ATLAS_MAX]; 149 | 150 | // Get movie area size 151 | SDL_RenderSetLogicalSize(renderer, pinfo.video_format.width, pinfo.video_format.height); 152 | while(run) { 153 | if(Kit_GetPlayerState(player) == KIT_STOPPED) { 154 | run = false; 155 | continue; 156 | } 157 | 158 | SDL_Event event; 159 | while(SDL_PollEvent(&event)) { 160 | switch(event.type) { 161 | case SDL_QUIT: 162 | run = false; 163 | break; 164 | case SDL_KEYUP: 165 | if(event.key.keysym.sym == SDLK_RIGHT) 166 | Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10); 167 | if(event.key.keysym.sym == SDLK_LEFT) 168 | Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10); 169 | break; 170 | } 171 | } 172 | 173 | // Refresh audio 174 | int queued = SDL_GetQueuedAudioSize(audio_dev); 175 | if(queued < AUDIO_BUFFER_SIZE) { 176 | int need = AUDIO_BUFFER_SIZE - queued; 177 | 178 | while(need > 0) { 179 | ret = Kit_GetPlayerAudioData(player, queued, (unsigned char *)audio_buf, AUDIO_BUFFER_SIZE); 180 | need -= ret; 181 | if(ret > 0) { 182 | SDL_QueueAudio(audio_dev, audio_buf, ret); 183 | } else { 184 | break; 185 | } 186 | } 187 | // If we now have data, start playback (again) 188 | if(SDL_GetQueuedAudioSize(audio_dev) > 0) { 189 | SDL_PauseAudioDevice(audio_dev, 0); 190 | } 191 | } 192 | 193 | // Refresh video texture and render it 194 | Kit_GetPlayerVideoSDLTexture(player, video_tex, NULL); 195 | SDL_RenderCopy(renderer, video_tex, NULL, NULL); 196 | 197 | // Refresh subtitle texture atlas and render subtitle frames from it 198 | // For subtitles, use screen size instead of video size for best quality 199 | int got = Kit_GetPlayerSubtitleSDLTexture(player, subtitle_tex, sources, targets, ATLAS_MAX); 200 | for(int i = 0; i < got; i++) { 201 | SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]); 202 | } 203 | 204 | // Render to screen + wait for vsync 205 | SDL_RenderPresent(renderer); 206 | } 207 | 208 | Kit_ClosePlayer(player); 209 | Kit_CloseSource(src); 210 | SDL_RWclose(rw_ops); 211 | Kit_Quit(); 212 | 213 | SDL_DestroyTexture(subtitle_tex); 214 | SDL_DestroyTexture(video_tex); 215 | SDL_CloseAudioDevice(audio_dev); 216 | 217 | SDL_DestroyRenderer(renderer); 218 | SDL_DestroyWindow(window); 219 | SDL_Quit(); 220 | return 0; 221 | } 222 | -------------------------------------------------------------------------------- /examples/example_custom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* 7 | * Note! This example does not do proper error handling etc. 8 | * It is for example use only! 9 | */ 10 | 11 | #define AUDIO_BUFFER_SIZE (1024 * 64) 12 | #define ATLAS_WIDTH 4096 13 | #define ATLAS_HEIGHT 4096 14 | #define ATLAS_MAX 1024 15 | 16 | int read_callback(void *userdata, uint8_t *buf, int buf_size) { 17 | FILE *fd = (FILE *)userdata; 18 | if(!feof(fd)) { 19 | return fread(buf, 1, buf_size, fd); 20 | } 21 | return -1; 22 | } 23 | 24 | int main(int argc, char *argv[]) { 25 | int err = 0, ret = 0; 26 | const char *filename = NULL; 27 | SDL_Window *window = NULL; 28 | SDL_Renderer *renderer = NULL; 29 | bool run = true; 30 | Kit_Source *src = NULL; 31 | Kit_Player *player = NULL; 32 | SDL_AudioSpec wanted_spec, audio_spec; 33 | SDL_AudioDeviceID audio_dev; 34 | 35 | // Get filename to open 36 | if(argc != 2) { 37 | fprintf(stderr, "Usage: custom \n"); 38 | return 0; 39 | } 40 | filename = argv[1]; 41 | 42 | // Init SDL 43 | err = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); 44 | if(err != 0) { 45 | fprintf(stderr, "Unable to initialize SDL2!\n"); 46 | return 1; 47 | } 48 | 49 | // Create a resizable window. 50 | window = 51 | SDL_CreateWindow(filename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE); 52 | if(window == NULL) { 53 | fprintf(stderr, "Unable to create a new window!\n"); 54 | return 1; 55 | } 56 | 57 | // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay. 58 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 59 | if(renderer == NULL) { 60 | fprintf(stderr, "Unable to create a renderer!\n"); 61 | return 1; 62 | } 63 | 64 | // Initialize Kitchensink with network and libass support. 65 | err = Kit_Init(KIT_INIT_NETWORK | KIT_INIT_ASS); 66 | if(err != 0) { 67 | fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); 68 | return 1; 69 | } 70 | 71 | // Open file with fopen. We then proceed to read this with our custom file handlers. 72 | FILE *fd = fopen(filename, "rb"); 73 | if(fd == NULL) { 74 | fprintf(stderr, "Unable to open file '%s' for reading\n", filename); 75 | return 1; 76 | } 77 | 78 | // Open up the custom source. Declare read callback, and transport FD in userdata. 79 | src = Kit_CreateSourceFromCustom(read_callback, NULL, fd); 80 | if(src == NULL) { 81 | fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); 82 | return 1; 83 | } 84 | 85 | // Create the player. Pick best video, audio and subtitle streams, and set subtitle 86 | // rendering resolution to screen resolution. 87 | player = Kit_CreatePlayer( 88 | src, 89 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO), 90 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO), 91 | Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE), 92 | NULL, 93 | NULL, 94 | 1280, 95 | 720 96 | ); 97 | if(player == NULL) { 98 | fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); 99 | return 1; 100 | } 101 | 102 | // Print some information 103 | Kit_PlayerInfo player_info; 104 | Kit_GetPlayerInfo(player, &player_info); 105 | 106 | // Make sure there is video in the file to play first. 107 | if(Kit_GetPlayerVideoStream(player) == -1) { 108 | fprintf(stderr, "File contains no video!\n"); 109 | return 1; 110 | } 111 | 112 | // Init audio 113 | SDL_memset(&wanted_spec, 0, sizeof(wanted_spec)); 114 | wanted_spec.freq = player_info.audio_format.sample_rate; 115 | wanted_spec.format = player_info.audio_format.format; 116 | wanted_spec.channels = player_info.audio_format.channels; 117 | audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0); 118 | SDL_PauseAudioDevice(audio_dev, 0); 119 | 120 | // Initialize video texture. This will probably end up as YV12 most of the time. 121 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); 122 | SDL_Texture *video_tex = SDL_CreateTexture( 123 | renderer, 124 | player_info.video_format.format, 125 | SDL_TEXTUREACCESS_STATIC, 126 | player_info.video_format.width, 127 | player_info.video_format.height 128 | ); 129 | if(video_tex == NULL) { 130 | fprintf(stderr, "Error while attempting to create a video texture\n"); 131 | return 1; 132 | } 133 | 134 | // This is the subtitle texture atlas. This contains all the subtitle image fragments. 135 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations 136 | SDL_Texture *subtitle_tex = SDL_CreateTexture( 137 | renderer, player_info.subtitle_format.format, SDL_TEXTUREACCESS_STATIC, ATLAS_WIDTH, ATLAS_HEIGHT 138 | ); 139 | if(subtitle_tex == NULL) { 140 | fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n"); 141 | return 1; 142 | } 143 | 144 | // Make sure subtitle texture is in correct blending mode 145 | SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND); 146 | 147 | // Clear screen with black 148 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 149 | SDL_RenderClear(renderer); 150 | 151 | // Start playback 152 | Kit_PlayerPlay(player); 153 | 154 | // Playback temporary data buffers 155 | char audio_buf[AUDIO_BUFFER_SIZE]; 156 | SDL_Rect sources[ATLAS_MAX]; 157 | SDL_Rect targets[ATLAS_MAX]; 158 | 159 | // Get movie area size 160 | SDL_RenderSetLogicalSize(renderer, player_info.video_format.width, player_info.video_format.height); 161 | while(run) { 162 | if(Kit_GetPlayerState(player) == KIT_STOPPED) { 163 | run = false; 164 | continue; 165 | } 166 | 167 | SDL_Event event; 168 | while(SDL_PollEvent(&event)) { 169 | switch(event.type) { 170 | case SDL_QUIT: 171 | run = false; 172 | break; 173 | case SDL_KEYUP: 174 | if(event.key.keysym.sym == SDLK_RIGHT) 175 | Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10); 176 | if(event.key.keysym.sym == SDLK_LEFT) 177 | Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10); 178 | break; 179 | } 180 | } 181 | 182 | // Refresh audio 183 | int queued = SDL_GetQueuedAudioSize(audio_dev); 184 | if(queued < AUDIO_BUFFER_SIZE) { 185 | int need = AUDIO_BUFFER_SIZE - queued; 186 | 187 | while(need > 0) { 188 | ret = Kit_GetPlayerAudioData(player, queued, (unsigned char *)audio_buf, AUDIO_BUFFER_SIZE); 189 | need -= ret; 190 | if(ret > 0) { 191 | SDL_QueueAudio(audio_dev, audio_buf, ret); 192 | } else { 193 | break; 194 | } 195 | } 196 | // If we now have data, start playback (again) 197 | if(SDL_GetQueuedAudioSize(audio_dev) > 0) { 198 | SDL_PauseAudioDevice(audio_dev, 0); 199 | } 200 | } 201 | 202 | // Refresh video texture and render it 203 | Kit_GetPlayerVideoSDLTexture(player, video_tex, NULL); 204 | SDL_RenderCopy(renderer, video_tex, NULL, NULL); 205 | 206 | // Refresh subtitle texture atlas and render subtitle frames from it 207 | // For subtitles, use screen size instead of video size for best quality 208 | int got = Kit_GetPlayerSubtitleSDLTexture(player, subtitle_tex, sources, targets, ATLAS_MAX); 209 | for(int i = 0; i < got; i++) { 210 | SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]); 211 | } 212 | 213 | // Render to screen + wait for vsync 214 | SDL_RenderPresent(renderer); 215 | } 216 | 217 | Kit_ClosePlayer(player); 218 | Kit_CloseSource(src); 219 | fclose(fd); 220 | Kit_Quit(); 221 | 222 | SDL_DestroyTexture(subtitle_tex); 223 | SDL_DestroyTexture(video_tex); 224 | SDL_CloseAudioDevice(audio_dev); 225 | 226 | SDL_DestroyRenderer(renderer); 227 | SDL_DestroyWindow(window); 228 | SDL_Quit(); 229 | return 0; 230 | } 231 | -------------------------------------------------------------------------------- /src/internal/kitpacketbuffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "kitchensink2/internal/kitpacketbuffer.h" 5 | #include "kitchensink2/internal/utils/kitlog.h" 6 | #include "kitchensink2/kiterror.h" 7 | 8 | struct Kit_PacketBuffer { 9 | void **packets; 10 | SDL_mutex *mutex; 11 | SDL_cond *can_read; 12 | SDL_cond *can_write; 13 | size_t head; 14 | size_t tail; 15 | size_t capacity; 16 | bool full; 17 | buf_obj_unref unref_cb; 18 | buf_obj_free free_cb; 19 | buf_obj_move move_cb; 20 | buf_obj_ref ref_cb; 21 | }; 22 | 23 | Kit_PacketBuffer *Kit_CreatePacketBuffer( 24 | size_t capacity, 25 | buf_obj_alloc alloc_cb, 26 | buf_obj_unref unref_cb, 27 | buf_obj_free free_cb, 28 | buf_obj_move move_cb, 29 | buf_obj_ref ref_cb 30 | ) { 31 | assert(capacity > 0); 32 | Kit_PacketBuffer *buffer = NULL; 33 | SDL_mutex *mutex = NULL; 34 | SDL_cond *can_write = NULL; 35 | SDL_cond *can_read = NULL; 36 | void **packets; 37 | 38 | if((can_write = SDL_CreateCond()) == NULL) { 39 | Kit_SetError("Unable to allocate writer conditional variable: %s", SDL_GetError()); 40 | goto error_0; 41 | } 42 | if((can_read = SDL_CreateCond()) == NULL) { 43 | Kit_SetError("Unable to allocate reader conditional variable: %s", SDL_GetError()); 44 | goto error_1; 45 | } 46 | if((mutex = SDL_CreateMutex()) == NULL) { 47 | Kit_SetError("Unable to allocate mutex: %s", SDL_GetError()); 48 | goto error_2; 49 | } 50 | if((packets = calloc(capacity, sizeof(void *))) == NULL) { 51 | Kit_SetError("Unable to allocate packet buffer"); 52 | goto error_3; 53 | } 54 | for(size_t i = 0; i < capacity; i++) { 55 | if((packets[i] = alloc_cb()) == NULL) { 56 | Kit_SetError("Unable to allocate av_packet"); 57 | goto error_4; 58 | } 59 | } 60 | if((buffer = malloc(sizeof(Kit_PacketBuffer))) == NULL) { 61 | Kit_SetError("Unable to allocate packet stream"); 62 | goto error_4; 63 | } 64 | 65 | buffer->packets = packets; 66 | buffer->can_write = can_write; 67 | buffer->can_read = can_read; 68 | buffer->mutex = mutex; 69 | buffer->capacity = capacity; 70 | buffer->head = 0; 71 | buffer->tail = 0; 72 | buffer->full = false; 73 | buffer->unref_cb = unref_cb; 74 | buffer->free_cb = free_cb; 75 | buffer->move_cb = move_cb; 76 | buffer->ref_cb = ref_cb; 77 | return buffer; 78 | 79 | error_4: 80 | for(size_t i = 0; i < capacity; i++) { 81 | if(packets[i] != NULL) { 82 | free_cb((void **)&packets[i]); 83 | } 84 | } 85 | free(packets); 86 | error_3: 87 | SDL_DestroyMutex(mutex); 88 | error_2: 89 | SDL_DestroyCond(can_read); 90 | error_1: 91 | SDL_DestroyCond(can_write); 92 | error_0: 93 | return NULL; 94 | } 95 | 96 | void Kit_FreePacketBuffer(Kit_PacketBuffer **ref) { 97 | if(!ref || !*ref) 98 | return; 99 | 100 | Kit_PacketBuffer *buffer = *ref; 101 | SDL_CondBroadcast(buffer->can_read); 102 | SDL_CondBroadcast(buffer->can_write); 103 | if(SDL_LockMutex(buffer->mutex) == 0) { 104 | for(size_t i = 0; i < buffer->capacity; i++) { 105 | buffer->free_cb((void **)&buffer->packets[i]); 106 | } 107 | SDL_UnlockMutex(buffer->mutex); 108 | } 109 | SDL_DestroyCond(buffer->can_read); 110 | SDL_DestroyCond(buffer->can_write); 111 | SDL_DestroyMutex(buffer->mutex); 112 | free(buffer->packets); 113 | free(buffer); 114 | *ref = NULL; 115 | } 116 | 117 | bool Kit_IsPacketBufferFull(const Kit_PacketBuffer *buffer) { 118 | assert(buffer); 119 | return buffer->full; 120 | } 121 | 122 | bool Kit_IsPacketBufferEmpty(const Kit_PacketBuffer *buffer) { 123 | assert(buffer); 124 | return (!buffer->full && (buffer->head == buffer->tail)); 125 | } 126 | 127 | size_t Kit_GetPacketBufferCapacity(const Kit_PacketBuffer *buffer) { 128 | assert(buffer); 129 | return buffer->capacity; 130 | } 131 | 132 | size_t Kit_GetPacketBufferLength(const Kit_PacketBuffer *buffer) { 133 | assert(buffer); 134 | if(buffer->full) 135 | return buffer->capacity; 136 | return (buffer->head >= buffer->tail) ? buffer->head - buffer->tail 137 | : buffer->capacity + buffer->head - buffer->tail; 138 | } 139 | 140 | void Kit_FlushPacketBuffer(Kit_PacketBuffer *buffer) { 141 | if(buffer == NULL) 142 | return; 143 | if(SDL_LockMutex(buffer->mutex) == 0) { 144 | for(size_t i = 0; i < buffer->capacity; i++) { 145 | buffer->unref_cb(buffer->packets[i]); 146 | } 147 | buffer->head = 0; 148 | buffer->tail = 0; 149 | buffer->full = false; 150 | SDL_UnlockMutex(buffer->mutex); 151 | SDL_CondSignal(buffer->can_write); 152 | } 153 | } 154 | 155 | void Kit_SignalPacketBuffer(Kit_PacketBuffer *buffer) { 156 | if(buffer == NULL) 157 | return; 158 | SDL_CondBroadcast(buffer->can_write); 159 | SDL_CondBroadcast(buffer->can_read); 160 | } 161 | 162 | static void advance_read(Kit_PacketBuffer *buffer) { 163 | assert(buffer); 164 | buffer->full = false; 165 | if(++(buffer->tail) == buffer->capacity) 166 | buffer->tail = 0; 167 | } 168 | 169 | static void advance_write(Kit_PacketBuffer *buffer) { 170 | assert(buffer); 171 | if(buffer->full) 172 | if(++(buffer->tail) == buffer->capacity) 173 | buffer->tail = 0; 174 | if(++(buffer->head) == buffer->capacity) 175 | buffer->head = 0; 176 | buffer->full = (buffer->head == buffer->tail); 177 | } 178 | 179 | bool Kit_WritePacketBuffer(Kit_PacketBuffer *buffer, void *src) { 180 | assert(buffer); 181 | assert(src); 182 | if(SDL_LockMutex(buffer->mutex) < 0) 183 | goto error_0; 184 | if(Kit_IsPacketBufferFull(buffer)) 185 | SDL_CondWait(buffer->can_write, buffer->mutex); 186 | if(Kit_IsPacketBufferFull(buffer)) 187 | goto error_1; 188 | buffer->move_cb(buffer->packets[buffer->head], src); 189 | advance_write(buffer); 190 | // LOG("WRITE -- HEAD = %lld, TAIL = %lld, USED = %lld/%lld\n", buffer->head, buffer->tail, 191 | // Kit_GetPacketBufferLength(buffer), buffer->capacity); 192 | SDL_UnlockMutex(buffer->mutex); 193 | SDL_CondSignal(buffer->can_read); 194 | return true; 195 | 196 | error_1: 197 | SDL_UnlockMutex(buffer->mutex); 198 | error_0: 199 | return false; 200 | } 201 | 202 | bool Kit_ReadPacketBuffer(Kit_PacketBuffer *buffer, void *dst, int timeout) { 203 | assert(buffer); 204 | if(SDL_LockMutex(buffer->mutex) < 0) 205 | goto error_0; 206 | if(Kit_IsPacketBufferEmpty(buffer)) { 207 | if(timeout <= 0) 208 | goto error_1; 209 | if(SDL_CondWaitTimeout(buffer->can_read, buffer->mutex, timeout) == SDL_MUTEX_TIMEDOUT) 210 | goto error_1; 211 | } 212 | if(Kit_IsPacketBufferEmpty(buffer)) 213 | goto error_1; 214 | buffer->move_cb(dst, buffer->packets[buffer->tail]); 215 | advance_read(buffer); 216 | // LOG("READ -- HEAD = %lld, TAIL = %lld, USED = %lld/%lld\n", buffer->head, buffer->tail, 217 | // Kit_GetPacketBufferLength(buffer), buffer->capacity); 218 | SDL_UnlockMutex(buffer->mutex); 219 | SDL_CondSignal(buffer->can_write); 220 | return true; 221 | 222 | error_1: 223 | SDL_UnlockMutex(buffer->mutex); 224 | error_0: 225 | return false; 226 | } 227 | 228 | bool Kit_BeginPacketBufferRead(Kit_PacketBuffer *buffer, void *dst, int timeout) { 229 | assert(buffer); 230 | if(SDL_LockMutex(buffer->mutex) < 0) 231 | goto error_0; 232 | if(Kit_IsPacketBufferEmpty(buffer)) { 233 | if(timeout <= 0) 234 | goto error_1; 235 | if(SDL_CondWaitTimeout(buffer->can_read, buffer->mutex, timeout) == SDL_MUTEX_TIMEDOUT) 236 | goto error_1; 237 | } 238 | if(Kit_IsPacketBufferEmpty(buffer)) 239 | goto error_1; 240 | buffer->ref_cb(dst, buffer->packets[buffer->tail]); 241 | // LOG("BEGIN -- HEAD = %lld, TAIL = %lld, USED = %lld/%lld\n", buffer->head, buffer->tail, 242 | // Kit_GetPacketBufferLength(buffer), buffer->capacity); 243 | return true; 244 | 245 | error_1: 246 | SDL_UnlockMutex(buffer->mutex); 247 | error_0: 248 | return false; 249 | } 250 | 251 | void Kit_FinishPacketBufferRead(Kit_PacketBuffer *buffer) { 252 | assert(buffer); 253 | buffer->unref_cb(buffer->packets[buffer->tail]); 254 | advance_read(buffer); 255 | SDL_UnlockMutex(buffer->mutex); 256 | // LOG("FINISH -- HEAD = %lld, TAIL = %lld, USED = %lld/%lld\n", buffer->head, buffer->tail, 257 | // Kit_GetPacketBufferLength(buffer), buffer->capacity); 258 | SDL_CondSignal(buffer->can_write); 259 | } 260 | 261 | void Kit_CancelPacketBufferRead(Kit_PacketBuffer *buffer) { 262 | assert(buffer); 263 | // LOG("CANCEL -- HEAD = %lld, TAIL = %lld, USED = %lld/%lld\n", buffer->head, buffer->tail, 264 | // Kit_GetPacketBufferLength(buffer), buffer->capacity); 265 | SDL_UnlockMutex(buffer->mutex); 266 | } -------------------------------------------------------------------------------- /src/internal/subtitle/kitsubtitle.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "kitchensink2/internal/kitlibstate.h" 7 | #include "kitchensink2/internal/subtitle/kitatlas.h" 8 | #include "kitchensink2/internal/subtitle/kitsubtitle.h" 9 | #include "kitchensink2/internal/subtitle/kitsubtitlepacket.h" 10 | #include "kitchensink2/internal/subtitle/renderers/kitsubass.h" 11 | #include "kitchensink2/internal/subtitle/renderers/kitsubimage.h" 12 | #include "kitchensink2/internal/subtitle/renderers/kitsubrenderer.h" 13 | #include "kitchensink2/kiterror.h" 14 | #include "kitchensink2/kitlib.h" 15 | 16 | typedef struct Kit_SubtitleDecoder { 17 | Kit_SubtitleRenderer *renderer; 18 | AVSubtitle scratch_frame; 19 | Kit_TextureAtlas *atlas; 20 | Kit_SubtitleOutputFormat output; 21 | } Kit_SubtitleDecoder; 22 | 23 | static void dec_read_subtitle(const Kit_Decoder *decoder, int64_t packet_pts) { 24 | Kit_SubtitleDecoder *subtitle_decoder = decoder->userdata; 25 | 26 | // Start and end presentation timestamps for subtitle frame 27 | double pts = 0; 28 | if(packet_pts != AV_NOPTS_VALUE) 29 | pts = packet_pts * av_q2d(decoder->stream->time_base); 30 | 31 | // If subtitle has no ending time, we set some safety value. 32 | if(subtitle_decoder->scratch_frame.end_display_time == UINT_MAX) 33 | subtitle_decoder->scratch_frame.end_display_time = 30000; 34 | 35 | const double start = subtitle_decoder->scratch_frame.start_display_time / 1000.0F; 36 | const double end = subtitle_decoder->scratch_frame.end_display_time / 1000.0F; 37 | 38 | // Create a packet. This should be filled by renderer. 39 | Kit_RunSubtitleRenderer(subtitle_decoder->renderer, &subtitle_decoder->scratch_frame, pts, start, end); 40 | } 41 | 42 | static void dec_flush_subtitle_cb(Kit_Decoder *decoder) { 43 | assert(decoder); 44 | Kit_SubtitleDecoder *subtitle_decoder = decoder->userdata; 45 | Kit_FlushSubtitleRendererBuffers(subtitle_decoder->renderer); 46 | } 47 | 48 | static void dec_signal_subtitle_cb(Kit_Decoder *decoder) { 49 | assert(decoder); 50 | Kit_SubtitleDecoder *subtitle_decoder = decoder->userdata; 51 | Kit_SignalSubtitleRenderer(subtitle_decoder->renderer); 52 | } 53 | 54 | static Kit_DecoderInputResult dec_input_subtitle_cb(const Kit_Decoder *dec, const AVPacket *in_packet) { 55 | assert(dec); 56 | 57 | Kit_SubtitleDecoder *subtitle_decoder = dec->userdata; 58 | int frame_finished; 59 | 60 | if(in_packet == NULL) 61 | return KIT_DEC_INPUT_EOF; 62 | if(in_packet->size <= 0) 63 | return KIT_DEC_INPUT_OK; 64 | if(avcodec_decode_subtitle2(dec->codec_ctx, &subtitle_decoder->scratch_frame, &frame_finished, in_packet) < 0) 65 | return KIT_DEC_INPUT_OK; 66 | if(frame_finished) { 67 | dec_read_subtitle(dec, in_packet->pts); 68 | avsubtitle_free(&subtitle_decoder->scratch_frame); 69 | } 70 | return KIT_DEC_INPUT_OK; 71 | } 72 | 73 | static bool dec_decode_subtitle_cb(const Kit_Decoder *dec, double *pts) { 74 | *pts = -1.0; 75 | return false; 76 | } 77 | 78 | static void dec_get_subtitle_buffers_cb(const Kit_Decoder *ref, unsigned int *length, unsigned int *capacity) { 79 | assert(ref); 80 | assert(ref->userdata); 81 | Kit_SubtitleDecoder *subtitle_decoder = ref->userdata; 82 | if(length != NULL) 83 | *length = subtitle_decoder->atlas->cur_items; 84 | if(capacity != NULL) 85 | *capacity = subtitle_decoder->atlas->max_items; 86 | } 87 | 88 | static void dec_close_subtitle_cb(Kit_Decoder *ref) { 89 | if(ref == NULL) 90 | return; 91 | assert(ref->userdata); 92 | Kit_SubtitleDecoder *subtitle_dec = ref->userdata; 93 | avsubtitle_free(&subtitle_dec->scratch_frame); 94 | if(subtitle_dec->atlas != NULL) 95 | Kit_FreeAtlas(subtitle_dec->atlas); 96 | if(subtitle_dec->renderer != NULL) 97 | Kit_CloseSubtitleRenderer(subtitle_dec->renderer); 98 | free(subtitle_dec); 99 | } 100 | 101 | int Kit_GetSubtitleDecoderOutputFormat(const Kit_Decoder *decoder, Kit_SubtitleOutputFormat *output) { 102 | if(decoder == NULL) { 103 | memset(output, 0, sizeof(Kit_SubtitleOutputFormat)); 104 | return 1; 105 | } 106 | Kit_SubtitleDecoder *subtitle_decoder = decoder->userdata; 107 | memcpy(output, &subtitle_decoder->output, sizeof(Kit_SubtitleOutputFormat)); 108 | return 0; 109 | } 110 | 111 | Kit_Decoder *Kit_CreateSubtitleDecoder( 112 | const Kit_Source *src, 113 | Kit_Timer *sync_timer, 114 | int stream_index, 115 | int video_w, 116 | int video_h, 117 | int screen_w, 118 | int screen_h 119 | ) { 120 | assert(src != NULL); 121 | assert(video_w >= 0); 122 | assert(video_h >= 0); 123 | assert(screen_w >= 0); 124 | assert(screen_h >= 0); 125 | 126 | const Kit_LibraryState *state = Kit_GetLibraryState(); 127 | const AVFormatContext *format_ctx = src->format_ctx; 128 | AVStream *stream = NULL; 129 | Kit_SubtitleDecoder *subtitle_decoder = NULL; 130 | Kit_Decoder *decoder = NULL; 131 | Kit_SubtitleOutputFormat output; 132 | Kit_SubtitleRenderer *renderer = NULL; 133 | Kit_TextureAtlas *atlas = NULL; 134 | 135 | // Find and set up stream. 136 | if(stream_index < 0 || stream_index >= format_ctx->nb_streams) { 137 | Kit_SetError("Invalid subtitle stream index %d", stream_index); 138 | return NULL; 139 | } 140 | stream = format_ctx->streams[stream_index]; 141 | 142 | if((subtitle_decoder = calloc(1, sizeof(Kit_SubtitleDecoder))) == NULL) { 143 | Kit_SetError("Unable to allocate audio decoder for stream %d", stream_index); 144 | goto exit_0; 145 | } 146 | if((decoder = Kit_CreateDecoder( 147 | stream, 148 | sync_timer, 149 | state->thread_count, 150 | KIT_HWDEVICE_TYPE_ALL, 151 | dec_input_subtitle_cb, 152 | dec_decode_subtitle_cb, 153 | dec_flush_subtitle_cb, 154 | dec_signal_subtitle_cb, 155 | dec_close_subtitle_cb, 156 | dec_get_subtitle_buffers_cb, 157 | subtitle_decoder 158 | )) == NULL) { 159 | // No need to Kit_SetError, it will be set in Kit_CreateDecoder. 160 | goto exit_1; 161 | } 162 | if((atlas = Kit_CreateAtlas()) == NULL) { 163 | Kit_SetError("Unable to allocate subtitle texture atlas for stream %d", stream_index); 164 | goto exit_2; 165 | } 166 | 167 | // Note -- format is always RGBA32, there is no point in using anything else due to libass favoring this. 168 | memset(&output, 0, sizeof(Kit_SubtitleOutputFormat)); 169 | output.format = SDL_PIXELFORMAT_RGBA32; 170 | 171 | // For subtitles, we need a renderer for the stream. Pick one based on codec ID. 172 | switch(decoder->codec_ctx->codec_id) { 173 | case AV_CODEC_ID_TEXT: 174 | case AV_CODEC_ID_HDMV_TEXT_SUBTITLE: 175 | case AV_CODEC_ID_SRT: 176 | case AV_CODEC_ID_SUBRIP: 177 | case AV_CODEC_ID_SSA: 178 | case AV_CODEC_ID_ASS: 179 | if(state->init_flags & KIT_INIT_ASS) { 180 | renderer = Kit_CreateASSSubtitleRenderer(format_ctx, decoder, video_w, video_h, screen_w, screen_h); 181 | } 182 | break; 183 | case AV_CODEC_ID_DVD_SUBTITLE: 184 | case AV_CODEC_ID_DVB_SUBTITLE: 185 | case AV_CODEC_ID_HDMV_PGS_SUBTITLE: 186 | renderer = Kit_CreateImageSubtitleRenderer(decoder, video_w, video_h, screen_w, screen_h); 187 | break; 188 | default: 189 | Kit_SetError("Unrecognized subtitle format for stream %d", stream_index); 190 | break; 191 | } 192 | if(renderer == NULL) { 193 | goto exit_3; 194 | } 195 | 196 | subtitle_decoder->atlas = atlas; 197 | subtitle_decoder->renderer = renderer; 198 | subtitle_decoder->output = output; 199 | return decoder; 200 | 201 | exit_3: 202 | Kit_FreeAtlas(atlas); 203 | exit_2: 204 | Kit_CloseDecoder(&decoder); 205 | return NULL; // Above frees the subtitle_decoder also. 206 | exit_1: 207 | free(subtitle_decoder); 208 | exit_0: 209 | return NULL; 210 | } 211 | 212 | void Kit_SetSubtitleDecoderSize(const Kit_Decoder *dec, int screen_w, int screen_h) { 213 | assert(dec != NULL); 214 | const Kit_SubtitleDecoder *subtitle_dec = dec->userdata; 215 | Kit_SetSubtitleRendererSize(subtitle_dec->renderer, screen_w, screen_h); 216 | } 217 | 218 | void Kit_GetSubtitleDecoderSDLTexture(const Kit_Decoder *dec, SDL_Texture *texture, double sync_ts) { 219 | assert(dec != NULL); 220 | assert(texture != NULL); 221 | 222 | const Kit_SubtitleDecoder *subtitle_dec = dec->userdata; 223 | Kit_GetSubtitleRendererSDLTexture(subtitle_dec->renderer, subtitle_dec->atlas, texture, sync_ts); 224 | } 225 | 226 | int Kit_GetSubtitleDecoderSDLTextureInfo(const Kit_Decoder *dec, SDL_Rect *sources, SDL_Rect *targets, int limit) { 227 | const Kit_SubtitleDecoder *subtitle_dec = dec->userdata; 228 | return Kit_GetAtlasItems(subtitle_dec->atlas, sources, targets, limit); 229 | } 230 | 231 | int Kit_GetSubtitleDecoderRawFrames( 232 | const Kit_Decoder *dec, unsigned char ***items, SDL_Rect **sources, SDL_Rect **targets, double sync_ts 233 | ) { 234 | Kit_SubtitleDecoder *subtitle_dec = dec->userdata; 235 | return Kit_GetSubtitleRendererRawFrames(subtitle_dec->renderer, items, sources, targets, sync_ts); 236 | } 237 | -------------------------------------------------------------------------------- /src/internal/kitdecoder.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "kitchensink2/internal/kitdecoder.h" 10 | #include "kitchensink2/internal/kitlibstate.h" 11 | #include "kitchensink2/internal/utils/kitlog.h" 12 | #include "kitchensink2/internal/video/kitvideoutils.h" 13 | #include "kitchensink2/kiterror.h" 14 | 15 | /** 16 | * Check if hardware context supports an output format that we can feed to swscale 17 | */ 18 | static bool Kit_TestSWFormat(const AVHWFramesConstraints *constraints) { 19 | enum AVPixelFormat *test; 20 | for(test = constraints->valid_sw_formats; *test != AV_PIX_FMT_NONE; test++) { 21 | if(sws_isSupportedInput(*test)) { 22 | return true; 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | /** 29 | * Make sure our hardware device seems like it can handle the video stream we want to feed it. 30 | */ 31 | static AVBufferRef *Kit_TestHWDevice(const AVCodecHWConfig *config, unsigned int w, unsigned int h) { 32 | AVBufferRef *hw_device_ctx; 33 | AVHWFramesConstraints *constraints; 34 | 35 | if(!(config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) { 36 | goto exit_0; 37 | } 38 | if(av_hwdevice_ctx_create(&hw_device_ctx, config->device_type, NULL, NULL, 0) != 0) { 39 | goto exit_0; 40 | } 41 | if((constraints = av_hwdevice_get_hwframe_constraints(hw_device_ctx, NULL)) == NULL) { 42 | goto exit_1; 43 | } 44 | if(constraints->max_height < h || constraints->min_height > h) { 45 | goto exit_2; 46 | } 47 | if(constraints->max_width < w || constraints->min_width > w) { 48 | goto exit_2; 49 | } 50 | if(!Kit_TestSWFormat(constraints)) { 51 | goto exit_2; 52 | } 53 | 54 | av_hwframe_constraints_free(&constraints); 55 | return hw_device_ctx; 56 | 57 | exit_2: 58 | av_hwframe_constraints_free(&constraints); 59 | exit_1: 60 | av_buffer_unref(&hw_device_ctx); 61 | exit_0: 62 | return NULL; 63 | } 64 | 65 | /** 66 | * Find an actual hardware decoder we can use. It must be able to decode our stream, and must output something 67 | * we can actually deal with or convert. 68 | */ 69 | static AVBufferRef *Kit_FindHardwareDecoder( 70 | const AVCodec *codec, 71 | unsigned int hw_device_types, 72 | unsigned int w, 73 | unsigned int h, 74 | enum AVHWDeviceType *type, 75 | enum AVPixelFormat *hw_fmt 76 | ) { 77 | const AVCodecHWConfig *config; 78 | 79 | for(int index = 0; (config = avcodec_get_hw_config(codec, index)) != NULL; index++) { 80 | Kit_HardwareDeviceType kit_type = Kit_FindHWDeviceType(config->device_type); 81 | AVBufferRef *hw_device_ctx = Kit_TestHWDevice(config, w, h); 82 | if(hw_device_types & kit_type && hw_device_ctx != NULL) { 83 | *type = config->device_type; 84 | *hw_fmt = config->pix_fmt; 85 | return hw_device_ctx; 86 | } 87 | } 88 | return NULL; 89 | } 90 | 91 | /** 92 | * Attempt to negotiate a hardware pixel format. If we can't find a good one, then just fall back to the 93 | * software format and disregard hardware. According to ffmpeg docs, it always suggests software format last, 94 | * so use that as a fallback. 95 | */ 96 | static enum AVPixelFormat Kit_GetHardwarePixelFormat(AVCodecContext *ctx, const enum AVPixelFormat *formats) { 97 | Kit_Decoder *decoder = ctx->opaque; 98 | enum AVPixelFormat prev = AV_PIX_FMT_NONE; 99 | const enum AVPixelFormat *fmt; 100 | for(fmt = formats; *fmt != AV_PIX_FMT_NONE; fmt++) { 101 | if(decoder->hw_fmt == *fmt) { 102 | return *fmt; 103 | } 104 | prev = *fmt; 105 | } 106 | // This is the last format (software format!) or AV_PIX_FMT_NONE. 107 | return prev; 108 | } 109 | 110 | Kit_Decoder *Kit_CreateDecoder( 111 | AVStream *stream, 112 | Kit_Timer *sync_timer, 113 | int thread_count, 114 | unsigned int hw_device_types, 115 | dec_input_cb dec_input, 116 | dec_decode_cb dec_decode, 117 | dec_flush_cb dec_flush, 118 | dec_signal_cb dec_signal, 119 | dec_close_cb dec_close, 120 | dec_get_buffers_cb dec_get_buffers, 121 | void *userdata 122 | ) { 123 | assert(stream != NULL); 124 | assert(thread_count >= 0); 125 | 126 | enum AVHWDeviceType hw_type = AV_HWDEVICE_TYPE_NONE; 127 | enum AVPixelFormat hw_fmt = AV_PIX_FMT_NONE; 128 | Kit_Decoder *decoder = NULL; 129 | AVCodecContext *codec_ctx = NULL; 130 | AVBufferRef *hw_device_ctx = NULL; 131 | AVDictionary *codec_opts = NULL; 132 | const AVCodec *codec = NULL; 133 | 134 | if((decoder = calloc(1, sizeof(Kit_Decoder))) == NULL) { 135 | Kit_SetError("Unable to allocate kit decoder for stream %d", stream->index); 136 | goto exit_0; 137 | } 138 | if((codec = avcodec_find_decoder(stream->codecpar->codec_id)) == NULL) { 139 | Kit_SetError("No suitable decoder found for stream %d", stream->index); 140 | goto exit_1; 141 | } 142 | if((codec_ctx = avcodec_alloc_context3(codec)) == NULL) { 143 | Kit_SetError("Unable to allocate codec context for stream %d", stream->index); 144 | goto exit_1; 145 | } 146 | if(avcodec_parameters_to_context(codec_ctx, stream->codecpar) < 0) { 147 | Kit_SetError("Unable to copy codec context for stream %d", stream->index); 148 | goto exit_2; 149 | } 150 | codec_ctx->pkt_timebase = stream->time_base; 151 | codec_ctx->opaque = decoder; // Used by Kit_GetHardwarePixelFormat() 152 | 153 | // Attempt to set up threading, if supported. 154 | codec_ctx->thread_count = thread_count; 155 | if(codec->capabilities | AV_CODEC_CAP_FRAME_THREADS) { 156 | codec_ctx->thread_type = FF_THREAD_FRAME; 157 | } else if(codec->capabilities | AV_CODEC_CAP_SLICE_THREADS) { 158 | codec_ctx->thread_type = FF_THREAD_SLICE; 159 | } else { 160 | codec_ctx->thread_count = 1; // Disable threading 161 | } 162 | 163 | // Try to initialize the hardware decoder for this codec. 164 | bool is_hw_enabled = Kit_GetLibraryState()->init_flags & KIT_INIT_HW_DECODE; 165 | if(is_hw_enabled) { 166 | hw_device_ctx = 167 | Kit_FindHardwareDecoder(codec, hw_device_types, codec_ctx->width, codec_ctx->height, &hw_type, &hw_fmt); 168 | if(hw_device_ctx != NULL) { 169 | codec_ctx->get_format = Kit_GetHardwarePixelFormat; 170 | codec_ctx->hw_device_ctx = hw_device_ctx; 171 | } 172 | } 173 | 174 | // Open the stream with selected options. Note that av_dict_set will allocate the dict! 175 | // This is required for ass_process_chunk() 176 | av_dict_set(&codec_opts, "sub_text_format", "ass", 0); 177 | if(avcodec_open2(codec_ctx, codec, &codec_opts) < 0) { 178 | Kit_SetError("Unable to open codec for stream %d", stream->index); 179 | goto exit_2; 180 | } 181 | av_dict_free(&codec_opts); 182 | 183 | decoder->stream = stream; 184 | decoder->stream = stream; 185 | decoder->sync_timer = sync_timer; 186 | decoder->codec_ctx = codec_ctx; 187 | decoder->hw_fmt = hw_fmt; 188 | decoder->hw_type = hw_type; 189 | decoder->dec_input = dec_input; 190 | decoder->dec_decode = dec_decode; 191 | decoder->dec_flush = dec_flush; 192 | decoder->dec_signal = dec_signal; 193 | decoder->dec_close = dec_close; 194 | decoder->dec_get_buffers = dec_get_buffers; 195 | decoder->userdata = userdata; 196 | return decoder; 197 | 198 | exit_2: 199 | av_dict_free(&codec_opts); 200 | avcodec_free_context(&codec_ctx); 201 | exit_1: 202 | free(decoder); 203 | exit_0: 204 | return NULL; 205 | } 206 | 207 | void Kit_CloseDecoder(Kit_Decoder **ref) { 208 | if(!ref || !*ref) 209 | return; 210 | Kit_Decoder *decoder = *ref; 211 | if(decoder->dec_close) 212 | decoder->dec_close(decoder); 213 | avcodec_free_context(&decoder->codec_ctx); 214 | Kit_CloseTimer(&decoder->sync_timer); 215 | free(decoder); 216 | *ref = NULL; 217 | } 218 | 219 | bool Kit_RunDecoder(const Kit_Decoder *decoder, double *pts) { 220 | assert(decoder); 221 | return decoder->dec_decode(decoder, pts); 222 | } 223 | 224 | Kit_DecoderInputResult Kit_AddDecoderPacket(const Kit_Decoder *decoder, const AVPacket *packet) { 225 | assert(decoder); 226 | return decoder->dec_input(decoder, packet); 227 | } 228 | 229 | void Kit_SignalDecoder(Kit_Decoder *decoder) { 230 | if(decoder == NULL) 231 | return; 232 | if(decoder->dec_signal) 233 | decoder->dec_signal(decoder); 234 | } 235 | 236 | void Kit_ClearDecoderBuffers(Kit_Decoder *decoder) { 237 | if(decoder == NULL) 238 | return; 239 | if(decoder->dec_flush) 240 | decoder->dec_flush(decoder); 241 | avcodec_flush_buffers(decoder->codec_ctx); 242 | } 243 | 244 | int Kit_GetDecoderCodecInfo(const Kit_Decoder *decoder, Kit_Codec *codec) { 245 | if(decoder == NULL) { 246 | memset(codec, 0, sizeof(Kit_Codec)); 247 | return 1; 248 | } 249 | codec->threads = decoder->codec_ctx->thread_count; 250 | snprintf(codec->name, KIT_CODEC_NAME_MAX, "%s", decoder->codec_ctx->codec->name); 251 | snprintf(codec->description, KIT_CODEC_DESC_MAX, "%s", decoder->codec_ctx->codec->long_name); 252 | return 0; 253 | } 254 | 255 | int Kit_GetDecoderBufferState(const Kit_Decoder *decoder, unsigned int *length, unsigned int *capacity) { 256 | if(decoder && decoder->dec_get_buffers) { 257 | decoder->dec_get_buffers(decoder, length, capacity); 258 | return 0; 259 | } 260 | return 1; 261 | } 262 | 263 | int Kit_GetDecoderStreamIndex(const Kit_Decoder *decoder) { 264 | if(!decoder) 265 | return -1; 266 | return decoder->stream->index; 267 | } 268 | -------------------------------------------------------------------------------- /src/kitsource.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "kitchensink2/internal/utils/kitlog.h" 9 | #include "kitchensink2/kiterror.h" 10 | #include "kitchensink2/kitsource.h" 11 | 12 | #define AVIO_BUF_SIZE 32768 13 | 14 | static int _ScanSource(AVFormatContext *format_ctx) { 15 | av_opt_set_int(format_ctx, "probesize", INT_MAX, 0); 16 | av_opt_set_int(format_ctx, "analyzeduration", INT_MAX, 0); 17 | if(avformat_find_stream_info(format_ctx, NULL) < 0) { 18 | Kit_SetError("Unable to fetch source information"); 19 | return 1; 20 | } 21 | return 0; 22 | } 23 | 24 | Kit_Source *Kit_CreateSourceFromUrl(const char *url) { 25 | assert(url != NULL); 26 | 27 | Kit_Source *src = calloc(1, sizeof(Kit_Source)); 28 | if(src == NULL) { 29 | Kit_SetError("Unable to allocate source"); 30 | return NULL; 31 | } 32 | 33 | // Attempt to open source 34 | if(avformat_open_input((AVFormatContext **)&src->format_ctx, url, NULL, NULL) < 0) { 35 | Kit_SetError("Unable to open source Url"); 36 | goto EXIT_0; 37 | } 38 | 39 | // Scan source information (may seek forwards) 40 | if(_ScanSource(src->format_ctx)) { 41 | goto EXIT_1; 42 | } 43 | 44 | return src; 45 | 46 | EXIT_1: 47 | avformat_close_input((AVFormatContext **)&src->format_ctx); 48 | EXIT_0: 49 | free(src); 50 | return NULL; 51 | } 52 | 53 | Kit_Source *Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallback seek_cb, void *userdata) { 54 | assert(read_cb != NULL); 55 | 56 | Kit_Source *src = calloc(1, sizeof(Kit_Source)); 57 | if(src == NULL) { 58 | Kit_SetError("Unable to allocate source"); 59 | return NULL; 60 | } 61 | 62 | uint8_t *avio_buf = av_malloc(AVIO_BUF_SIZE); 63 | if(avio_buf == NULL) { 64 | Kit_SetError("Unable to allocate avio buffer"); 65 | goto EXIT_0; 66 | } 67 | 68 | AVFormatContext *format_ctx = avformat_alloc_context(); 69 | if(format_ctx == NULL) { 70 | Kit_SetError("Unable to allocate format context"); 71 | goto EXIT_1; 72 | } 73 | 74 | AVIOContext *avio_ctx = avio_alloc_context(avio_buf, AVIO_BUF_SIZE, 0, userdata, read_cb, 0, seek_cb); 75 | if(avio_ctx == NULL) { 76 | Kit_SetError("Unable to allocate avio context"); 77 | goto EXIT_2; 78 | } 79 | 80 | // Set the format as AVIO format 81 | format_ctx->pb = avio_ctx; 82 | 83 | // Attempt to open source 84 | if(avformat_open_input(&format_ctx, "", NULL, NULL) < 0) { 85 | Kit_SetError("Unable to open custom source"); 86 | goto EXIT_3; 87 | } 88 | 89 | // Scan source information (may seek forwards) 90 | if(_ScanSource(format_ctx)) { 91 | goto EXIT_4; 92 | } 93 | 94 | // Set internals 95 | src->format_ctx = format_ctx; 96 | src->avio_ctx = avio_ctx; 97 | return src; 98 | 99 | EXIT_4: 100 | avformat_close_input(&format_ctx); 101 | EXIT_3: 102 | av_freep(&avio_ctx); 103 | EXIT_2: 104 | avformat_free_context(format_ctx); 105 | EXIT_1: 106 | av_freep(&avio_buf); 107 | EXIT_0: 108 | free(src); 109 | return NULL; 110 | } 111 | 112 | static int _RWReadCallback(void *userdata, uint8_t *buf, int size) { 113 | size_t bytes_read = SDL_RWread((SDL_RWops *)userdata, buf, 1, size); 114 | return bytes_read == 0 ? AVERROR_EOF : bytes_read; 115 | } 116 | 117 | static int64_t _RWGetSize(SDL_RWops *rw_ops) { 118 | // First, see if tell works at all, and fail with -1 if it doesn't. 119 | const int64_t current_pos = SDL_RWtell(rw_ops); 120 | if(current_pos < 0) { 121 | return -1; 122 | } 123 | 124 | // Seek to end, get pos (this is the size), then return. 125 | if(SDL_RWseek(rw_ops, 0, RW_SEEK_END) < 0) { 126 | return -1; // Seek failed, never mind then 127 | } 128 | const int64_t max_pos = SDL_RWtell(rw_ops); 129 | SDL_RWseek(rw_ops, current_pos, RW_SEEK_SET); 130 | return max_pos; 131 | } 132 | 133 | static int64_t _RWSeekCallback(void *userdata, int64_t offset, int whence) { 134 | int rw_whence = 0; 135 | if(whence & AVSEEK_SIZE) 136 | return _RWGetSize(userdata); 137 | 138 | if((whence & ~AVSEEK_FORCE) == SEEK_CUR) 139 | rw_whence = RW_SEEK_CUR; 140 | else if((whence & ~AVSEEK_FORCE) == SEEK_SET) 141 | rw_whence = RW_SEEK_SET; 142 | else if((whence & ~AVSEEK_FORCE) == SEEK_END) 143 | rw_whence = RW_SEEK_END; 144 | 145 | return SDL_RWseek((SDL_RWops *)userdata, offset, rw_whence); 146 | } 147 | 148 | Kit_Source *Kit_CreateSourceFromRW(SDL_RWops *rw_ops) { 149 | return Kit_CreateSourceFromCustom(_RWReadCallback, _RWSeekCallback, rw_ops); 150 | } 151 | 152 | void Kit_CloseSource(Kit_Source *src) { 153 | assert(src != NULL); 154 | AVFormatContext *format_ctx = src->format_ctx; 155 | AVIOContext *avio_ctx = src->avio_ctx; 156 | avformat_close_input(&format_ctx); 157 | if(avio_ctx) { 158 | av_freep(&avio_ctx->buffer); 159 | av_freep(&avio_ctx); 160 | } 161 | free(src); 162 | } 163 | 164 | static Kit_StreamType Kit_GetKitStreamType(const enum AVMediaType type) { 165 | switch(type) { 166 | case AVMEDIA_TYPE_DATA: 167 | return KIT_STREAMTYPE_DATA; 168 | case AVMEDIA_TYPE_VIDEO: 169 | return KIT_STREAMTYPE_VIDEO; 170 | case AVMEDIA_TYPE_AUDIO: 171 | return KIT_STREAMTYPE_AUDIO; 172 | case AVMEDIA_TYPE_SUBTITLE: 173 | return KIT_STREAMTYPE_SUBTITLE; 174 | case AVMEDIA_TYPE_ATTACHMENT: 175 | return KIT_STREAMTYPE_ATTACHMENT; 176 | default: 177 | return KIT_STREAMTYPE_UNKNOWN; 178 | } 179 | } 180 | 181 | int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_SourceStreamInfo *info, int index) { 182 | assert(src != NULL); 183 | assert(info != NULL); 184 | 185 | const AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx; 186 | if(index < 0 || index >= format_ctx->nb_streams) { 187 | Kit_SetError("Invalid stream index"); 188 | return 1; 189 | } 190 | 191 | const AVStream *stream = format_ctx->streams[index]; 192 | info->type = Kit_GetKitStreamType(stream->codecpar->codec_type); 193 | info->index = index; 194 | return 0; 195 | } 196 | 197 | static bool Kit_IsSubtitleSupported(const enum AVCodecID type) { 198 | switch(type) { 199 | case AV_CODEC_ID_TEXT: 200 | case AV_CODEC_ID_HDMV_TEXT_SUBTITLE: 201 | case AV_CODEC_ID_SRT: 202 | case AV_CODEC_ID_SUBRIP: 203 | case AV_CODEC_ID_SSA: 204 | case AV_CODEC_ID_ASS: 205 | return true; // Text types 206 | case AV_CODEC_ID_DVD_SUBTITLE: 207 | case AV_CODEC_ID_DVB_SUBTITLE: 208 | case AV_CODEC_ID_HDMV_PGS_SUBTITLE: 209 | return true; // Image types 210 | default: 211 | return false; 212 | } 213 | } 214 | 215 | static int Kit_GetBestSubtitleStream(const Kit_Source *src) { 216 | AVFormatContext *format_ctx = src->format_ctx; 217 | for(int i = 0; i < format_ctx->nb_streams; i++) { 218 | const AVStream *stream = format_ctx->streams[i]; 219 | if(stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE && 220 | Kit_IsSubtitleSupported(stream->codecpar->codec_id)) { 221 | return i; 222 | } 223 | } 224 | return -1; 225 | } 226 | 227 | int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_StreamType type) { 228 | assert(src != NULL); 229 | int avmedia_type = 0; 230 | switch(type) { 231 | case KIT_STREAMTYPE_VIDEO: 232 | avmedia_type = AVMEDIA_TYPE_VIDEO; 233 | break; 234 | case KIT_STREAMTYPE_AUDIO: 235 | avmedia_type = AVMEDIA_TYPE_AUDIO; 236 | break; 237 | case KIT_STREAMTYPE_SUBTITLE: 238 | return Kit_GetBestSubtitleStream(src); 239 | default: 240 | return -1; 241 | } 242 | int ret = av_find_best_stream((AVFormatContext *)src->format_ctx, avmedia_type, -1, -1, NULL, 0); 243 | if(ret == AVERROR_STREAM_NOT_FOUND) { 244 | return -1; 245 | } 246 | if(ret == AVERROR_DECODER_NOT_FOUND) { 247 | Kit_SetError("Unable to find a decoder for the stream"); 248 | return 1; 249 | } 250 | return ret; 251 | } 252 | 253 | int Kit_GetSourceStreamCount(const Kit_Source *src) { 254 | assert(src != NULL); 255 | return ((AVFormatContext *)src->format_ctx)->nb_streams; 256 | } 257 | 258 | double Kit_GetSourceDuration(const Kit_Source *src) { 259 | assert(src != NULL); 260 | return (((AVFormatContext *)src->format_ctx)->duration / AV_TIME_BASE); 261 | } 262 | 263 | int Kit_GetSourceStreamList(const Kit_Source *src, const Kit_StreamType type, int *list, int size) { 264 | int p = 0; 265 | AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx; 266 | Kit_StreamType found; 267 | 268 | for(int n = 0; n < Kit_GetSourceStreamCount(src); n++) { 269 | found = Kit_GetKitStreamType(format_ctx->streams[n]->codecpar->codec_type); 270 | if(found == type) { 271 | if(p >= size) 272 | return p; 273 | list[p++] = n; 274 | } 275 | } 276 | return p; 277 | } 278 | 279 | int Kit_GetNextSourceStream(const Kit_Source *src, const Kit_StreamType type, int current_index, int loop) { 280 | AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx; 281 | 282 | for(int n = current_index + 1; n < Kit_GetSourceStreamCount(src); n++) 283 | if(Kit_GetKitStreamType(format_ctx->streams[n]->codecpar->codec_type) == type) 284 | return n; 285 | 286 | if(!loop) 287 | return -1; 288 | 289 | for(int n = 0; n < current_index; n++) { 290 | if(Kit_GetKitStreamType(format_ctx->streams[n]->codecpar->codec_type) == type) 291 | return n; 292 | } 293 | 294 | return -1; 295 | } -------------------------------------------------------------------------------- /include/kitchensink2/kitsource.h: -------------------------------------------------------------------------------- 1 | #ifndef KITSOURCE_H 2 | #define KITSOURCE_H 3 | 4 | /** 5 | * @brief Video/Audio source file handling 6 | * 7 | * @file kitsource.h 8 | * @author Tuomas Virtanen 9 | * @date 2018-06-27 10 | */ 11 | 12 | #include "kitchensink2/kitconfig.h" 13 | #include 14 | #include 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | /** 21 | * @brief Type of the stream. 22 | * 23 | * This is used by Kit_SourceStreamInfo and Kit_GetSourceStreamInfo(). 24 | */ 25 | typedef enum Kit_StreamType 26 | { 27 | KIT_STREAMTYPE_UNKNOWN, ///< Unknown stream type 28 | KIT_STREAMTYPE_VIDEO, ///< Video stream 29 | KIT_STREAMTYPE_AUDIO, ///< Audio stream 30 | KIT_STREAMTYPE_DATA, ///< Data stream 31 | KIT_STREAMTYPE_SUBTITLE, ///< Subtitle stream 32 | KIT_STREAMTYPE_ATTACHMENT ///< Attachment stream (images, etc) 33 | } Kit_StreamType; 34 | 35 | /** 36 | * @brief Audio/video source. 37 | * 38 | * Should be created using Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom(), and 39 | * closed with Kit_CloseSource(). 40 | * 41 | * Source must exist for the whole duration of using a player. You must take care of closing the source 42 | * yourself after you are done with it! 43 | */ 44 | typedef struct Kit_Source { 45 | void *format_ctx; ///< FFmpeg: Videostream format context 46 | void *avio_ctx; ///< FFmpeg: AVIO context 47 | } Kit_Source; 48 | 49 | /** 50 | * @brief Information for a source stream. 51 | * 52 | * Fetch information by using Kit_GetSourceStreamInfo(). 53 | */ 54 | typedef struct Kit_SourceStreamInfo { 55 | int index; ///< Stream index 56 | Kit_StreamType type; ///< Stream type 57 | } Kit_SourceStreamInfo; 58 | 59 | /** 60 | * @brief Callback function type for reading data stream 61 | * 62 | * Used by Kit_CreateSourceFromCustom() for reading data from user defined source. 63 | * 64 | * A custom reader function must accept three arguments: 65 | * - userdata, this is the same data as set as last argument for Kit_CreateSourceFromCustom 66 | * - buf, a buffer the data must be copied into 67 | * - size, how much data you are expected to provide at maximum. 68 | * 69 | * The function must return the amount of bytes copied to the buffer or <0 on error. 70 | * 71 | * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation 72 | * for any further details. 73 | */ 74 | typedef int (*Kit_ReadCallback)(void *userdata, uint8_t *buf, int size); 75 | 76 | /** 77 | * @brief Callback function type for seeking data stream 78 | * 79 | * Used by Kit_CreateSourceFromCustom() for seeking a user defined source. 80 | * 81 | * A custom seeking function must accept three arguments: 82 | * - userdata, this is the same data as set as last argument for Kit_CreateSourceFromCustom 83 | * - offset, an seeking offset in bytes 84 | * - whence, reference position for the offset. 85 | * 86 | * Whence parameter can be one of the standard fseek values or optionally AVSEEK_SIZE. 87 | * - SEEK_SET: Reference position is beginning of file 88 | * - SEEK_CUR: Reference position is the current position of the file pointer 89 | * - SEEK_END: Reference position is the end of the file 90 | * - AVSEEK_SIZE: Optional. Does not seek, instead finds the size of the source file. 91 | * - AVSEEK_FORCE: Optional. Suggests that seeking should be done at any cost. May be passed alongside 92 | * any of the SEEK_* flags, eg. SEEK_SET|AVSEEK_FORCE. 93 | * 94 | * The function must return the position (in bytes) we seeked to or <0 on error or on unsupported operation. 95 | * 96 | * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation 97 | * for any further details. 98 | */ 99 | typedef int64_t (*Kit_SeekCallback)(void *userdata, int64_t offset, int whence); 100 | 101 | /** 102 | * @brief Create a new source from a given url 103 | * 104 | * This can be used to load video/audio from a file or network resource. If you wish to 105 | * use network resources, make sure the library has been initialized using KIT_INIT_NETWORK flag. 106 | * 107 | * This function will return an initialized Kit_Source on success. Note that you need to manually 108 | * free the source when it's no longer needed by calling Kit_CloseSource(). 109 | * 110 | * On failure, this function will return NULL, and further error data is available via Kit_GetError(). 111 | * 112 | * For example: 113 | * ``` 114 | * Kit_Source *src = Kit_CreateSourceFromUrl(filename); 115 | * if(src == NULL) { 116 | * fprintf(stderr, "Error: %s\n", Kit_GetError()); 117 | * return 1; 118 | * } 119 | * ``` 120 | * 121 | * @param url File path or URL to a video/audio resource 122 | * @return Returns an initialized Kit_Source* on success or NULL on failure 123 | */ 124 | KIT_API Kit_Source *Kit_CreateSourceFromUrl(const char *url); 125 | 126 | /** 127 | * @brief Create a new source from custom data 128 | * 129 | * This can be used to load data from any resource via the given read and seek functions. 130 | * 131 | * This function will return an initialized Kit_Source on success. Note that you need to manually 132 | * free the source when it's no longer needed by calling Kit_CloseSource(). 133 | * 134 | * On failure, this function will return NULL, and further error data is available via Kit_GetError(). 135 | * 136 | * For example: 137 | * ``` 138 | * Kit_Source *src = Kit_CreateSourceFromCustom(read_fn, seek_fn, fp); 139 | * if(src == NULL) { 140 | * fprintf(stderr, "Error: %s\n", Kit_GetError()); 141 | * return 1; 142 | * } 143 | * ``` 144 | * 145 | * @param read_cb Read function callback 146 | * @param seek_cb Seek function callback 147 | * @param userdata Any data (or NULL). Will be passed to read_cb and/or seek_cb functions as-is. 148 | * @return Returns an initialized Kit_Source* on success or NULL on failure 149 | */ 150 | KIT_API Kit_Source *Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallback seek_cb, void *userdata); 151 | 152 | /** 153 | * @brief Create a new source from SDL RWops struct 154 | * 155 | * Can be used to read data from SDL compatible sources. 156 | * 157 | * This function will return an initialized Kit_Source on success. Note that you need to manually 158 | * free the source when it's no longer needed by calling Kit_CloseSource(). 159 | * 160 | * On failure, this function will return NULL, and further error data is available via Kit_GetError(). 161 | * 162 | * Note that the RWops struct must exist during the whole lifetime of the source, and you must take 163 | * care of freeing the rwops after it's no longer needed. 164 | * 165 | * For example: 166 | * ``` 167 | * SDL_RWops *rw = SDL_RWFromFile("myvideo.mkv", "rb"); 168 | * Kit_Source *src = Kit_CreateSourceFromRW(rw); 169 | * if(src == NULL) { 170 | * fprintf(stderr, "Error: %s\n", Kit_GetError()); 171 | * return 1; 172 | * } 173 | * ``` 174 | * 175 | * @param rw_ops Initialized RWOps 176 | * @return KIT_API* Kit_CreateSourceFromRW 177 | */ 178 | KIT_API Kit_Source *Kit_CreateSourceFromRW(SDL_RWops *rw_ops); 179 | 180 | /** 181 | * @brief Closes a previously initialized source 182 | * 183 | * Closes a Kit_Source that was previously created by Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom() 184 | * and frees up all memory and resources used by it. Using the source for anything after this will 185 | * lead to undefined behaviour. 186 | * 187 | * Passing NULL as argument is valid, and will do nothing. 188 | * 189 | * @param src Previously initialized Kit_Source to close 190 | */ 191 | KIT_API void Kit_CloseSource(Kit_Source *src); 192 | 193 | /** 194 | * @brief Fetches stream information for a given stream index 195 | * 196 | * Sets fields for given Kit_SourceStreamInfo with information about the stream. 197 | * 198 | * For example: 199 | * ``` 200 | * Kit_SourceStreamInfo stream; 201 | * if(Kit_GetSourceStreamInfo(source, &stream, 0) == 1) { 202 | * fprintf(stderr, "Error: %s\n", Kit_GetError()); 203 | * return 1; 204 | * } 205 | * fprintf(stderr, "Stream type: %s\n", Kit_GetKitStreamTypeString(stream.type)) 206 | * ``` 207 | * 208 | * @param src Source to query from 209 | * @param info A previously allocated Kit_SourceStreamInfo to fill out 210 | * @param index Stream index (starting from 0) 211 | * @return 0 on success, 1 on error. 212 | */ 213 | KIT_API int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_SourceStreamInfo *info, int index); 214 | 215 | /** 216 | * @brief Gets the amount of streams in source 217 | * 218 | * @param src Source to query from 219 | * @return Number of streams in the source 220 | */ 221 | KIT_API int Kit_GetSourceStreamCount(const Kit_Source *src); 222 | 223 | /** 224 | * @brief Gets the best stream index for a given stream type. 225 | * 226 | * Find the best stream index for a given stream type, if one exists. If there is no 227 | * stream for the wanted type, will return -1. 228 | * 229 | * @param src Source to query from 230 | * @param type Stream type 231 | * @return Index number on success (>=0), -1 on error. 232 | */ 233 | KIT_API int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_StreamType type); 234 | 235 | /** 236 | * @brief Gets the approximate duration of the source. 237 | * 238 | * Note that the duration may or may not be totally correct. To get a better value, 239 | * open the source using the player, and use Kit_GetPlayerDuration() instead. 240 | * 241 | * @param src Source to query from 242 | * @return Duration of the source in seconds 243 | */ 244 | KIT_API double Kit_GetSourceDuration(const Kit_Source *src); 245 | 246 | /** 247 | * @brief Makes a list of stream indexes with requested type 248 | * 249 | * This can be used to get all stream indexes of certain type, eg. all video streams. 250 | * 251 | * @param src Source to query from 252 | * @param type Stream type to search 253 | * @param list Integer list to insert into 254 | * @param size Maximum size of the list 255 | * @return Number of elements found 256 | */ 257 | KIT_API int Kit_GetSourceStreamList(const Kit_Source *src, const Kit_StreamType type, int *list, int size); 258 | 259 | /** 260 | * @brief Gets the next stream index of given type 261 | * 262 | * This can be used to get the next stream index of a certain type, eg. a video stream. 263 | * 264 | * @param src Source to query from 265 | * @param type Stream type to search 266 | * @param current Index to to start iterating from 267 | * @param loop Start looping from the start of the stream list if we go past the end. 268 | * @return Index number if found, -1 if no more streams of given type were found. 269 | */ 270 | KIT_API int Kit_GetNextSourceStream(const Kit_Source *src, const Kit_StreamType type, int current_index, int loop); 271 | 272 | #ifdef __cplusplus 273 | } 274 | #endif 275 | 276 | #endif // KITSOURCE_H 277 | -------------------------------------------------------------------------------- /src/internal/subtitle/renderers/kitsubass.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "kitchensink2/internal/kitlibstate.h" 7 | #include "kitchensink2/internal/subtitle/kitatlas.h" 8 | #include "kitchensink2/internal/subtitle/renderers/kitsubass.h" 9 | #include "kitchensink2/internal/utils/kithelpers.h" 10 | #include "kitchensink2/kiterror.h" 11 | 12 | typedef struct Kit_ASSSubtitleRenderer { 13 | ASS_Renderer *renderer; 14 | ASS_Track *track; 15 | SDL_mutex *decoder_lock; 16 | unsigned char **cached_items; 17 | SDL_Rect *cached_dst_rects; 18 | SDL_Rect *cached_src_rects; 19 | unsigned int cached_items_size; 20 | } Kit_ASSSubtitleRenderer; 21 | 22 | static void Kit_ProcessAssImage(unsigned char *dst, const ASS_Image *img, int pitch) { 23 | const unsigned char r = ((img->color) >> 24) & 0xFF; 24 | const unsigned char g = ((img->color) >> 16) & 0xFF; 25 | const unsigned char b = ((img->color) >> 8) & 0xFF; 26 | const unsigned char a = 0xFF - ((img->color) & 0xFF); 27 | const unsigned char *src = img->bitmap; 28 | 29 | for(unsigned int y = 0; y < img->h; y++) { 30 | for(unsigned int x = 0; x < img->w; x++) { 31 | const unsigned int rx = x * 4; 32 | dst[rx + 0] = r; 33 | dst[rx + 1] = g; 34 | dst[rx + 2] = b; 35 | dst[rx + 3] = (a * src[x]) >> 8; 36 | } 37 | src += img->stride; 38 | dst += pitch; 39 | } 40 | } 41 | 42 | static void ren_render_ass_cb(Kit_SubtitleRenderer *renderer, void *src, double pts, double start, double end) { 43 | assert(renderer != NULL); 44 | assert(src != NULL); 45 | 46 | const Kit_ASSSubtitleRenderer *ass_renderer = renderer->userdata; 47 | const AVSubtitle *sub = src; 48 | 49 | // Read incoming subtitle packets to libASS 50 | SDL_LockMutex(ass_renderer->decoder_lock); 51 | const long long start_ms = (start + pts) * 1000; 52 | const long long end_ms = end * 1000; 53 | for(int r = 0; r < sub->num_rects; r++) { 54 | if(sub->rects[r]->ass == NULL) 55 | continue; 56 | 57 | // This requires the sub_text_format codec_opt set for ffmpeg 58 | ass_process_chunk(ass_renderer->track, sub->rects[r]->ass, strlen(sub->rects[r]->ass), start_ms, end_ms); 59 | } 60 | SDL_UnlockMutex(ass_renderer->decoder_lock); 61 | } 62 | 63 | static void ren_close_ass_cb(Kit_SubtitleRenderer *renderer) { 64 | if(!renderer || !renderer->userdata) 65 | return; 66 | Kit_ASSSubtitleRenderer *ass_renderer = renderer->userdata; 67 | ass_free_track(ass_renderer->track); 68 | ass_renderer_done(ass_renderer->renderer); 69 | SDL_DestroyMutex(ass_renderer->decoder_lock); 70 | for(unsigned int i = 0; i < ass_renderer->cached_items_size; i++) { 71 | free(ass_renderer->cached_items[i]); 72 | } 73 | free(ass_renderer->cached_items); 74 | free(ass_renderer->cached_dst_rects); 75 | free(ass_renderer->cached_src_rects); 76 | free(ass_renderer); 77 | } 78 | 79 | static ASS_Image *Kit_BeginReadFrames(const Kit_SubtitleRenderer *renderer, int *change, const double current_pts) { 80 | const Kit_ASSSubtitleRenderer *ass_renderer = renderer->userdata; 81 | const long long now = current_pts * 1000; 82 | 83 | // Tell ASS to render some images 84 | SDL_LockMutex(ass_renderer->decoder_lock); 85 | ASS_Image *src = ass_render_frame(ass_renderer->renderer, ass_renderer->track, now, change); 86 | SDL_UnlockMutex(ass_renderer->decoder_lock); 87 | return src; 88 | } 89 | 90 | static int ren_get_ass_data_cb( 91 | Kit_SubtitleRenderer *renderer, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 92 | ) { 93 | int change = 0; 94 | const ASS_Image *src = Kit_BeginReadFrames(renderer, &change, current_pts); 95 | if(change == 0) { 96 | return 0; 97 | } 98 | 99 | // There was some change, clear old atlas and check it's the same size as texture. 100 | Kit_ClearAtlasContent(atlas); 101 | Kit_CheckAtlasTextureSize(atlas, texture); 102 | 103 | // Process images and add them to atlas 104 | SDL_Surface *dst = NULL; 105 | int dst_w = 0, dst_h = 0; 106 | for(; src; src = src->next) { 107 | if(src->w == 0 || src->h == 0) 108 | continue; 109 | 110 | // Don't recreate surface if we already have correctly sized one. 111 | if(dst == NULL || dst_w != src->w || dst_h != src->h) { 112 | dst_w = src->w; 113 | dst_h = src->h; 114 | SDL_FreeSurface(dst); 115 | dst = SDL_CreateRGBSurfaceWithFormat(0, dst_w, dst_h, 32, SDL_PIXELFORMAT_RGBA32); 116 | } 117 | 118 | Kit_ProcessAssImage(dst->pixels, src, dst->pitch); 119 | SDL_Rect target; 120 | target.x = src->dst_x; 121 | target.y = src->dst_y; 122 | target.w = dst->w; 123 | target.h = dst->h; 124 | Kit_AddAtlasItem(atlas, texture, dst, &target); 125 | } 126 | 127 | SDL_FreeSurface(dst); 128 | return 0; 129 | } 130 | 131 | static unsigned int Kit_GetASSFramesNum(const ASS_Image *src) { 132 | unsigned int count = 0; 133 | for(; src; src = src->next) { 134 | if(src->w == 0 || src->h == 0) 135 | continue; 136 | count++; 137 | } 138 | return count; 139 | } 140 | 141 | static int ren_get_ass_raw_frames_cb( 142 | Kit_SubtitleRenderer *renderer, unsigned char ***frames, SDL_Rect **sources, SDL_Rect **targets, double current_pts 143 | ) { 144 | Kit_ASSSubtitleRenderer *ass_renderer = renderer->userdata; 145 | int change = 0; 146 | const ASS_Image *src = Kit_BeginReadFrames(renderer, &change, current_pts); 147 | if(change == 0) { 148 | goto get_cached; 149 | } 150 | 151 | // Something happened, so free up the old subtitle data first. 152 | for(unsigned int i = 0; i < ass_renderer->cached_items_size; i++) { 153 | free(ass_renderer->cached_items[i]); 154 | } 155 | 156 | // Figure out if we have any rects to render after we prune the zero-sized ones 157 | ass_renderer->cached_items_size = Kit_GetASSFramesNum(src); 158 | if(ass_renderer->cached_items_size == 0) { 159 | goto get_cached; 160 | } 161 | 162 | // Generate new RGBA images from libass surfaces and cache them. 163 | unsigned int index = 0; 164 | ass_renderer->cached_items = 165 | realloc(ass_renderer->cached_items, ass_renderer->cached_items_size * sizeof(unsigned char *)); 166 | ass_renderer->cached_dst_rects = 167 | realloc(ass_renderer->cached_dst_rects, ass_renderer->cached_items_size * sizeof(SDL_Rect)); 168 | ass_renderer->cached_src_rects = 169 | realloc(ass_renderer->cached_src_rects, ass_renderer->cached_items_size * sizeof(SDL_Rect)); 170 | for(; src; src = src->next) { 171 | if(src->w == 0 || src->h == 0) 172 | continue; 173 | 174 | unsigned char *buf = malloc(src->w * src->h * 4); 175 | Kit_ProcessAssImage(buf, src, src->w * 4); 176 | ass_renderer->cached_items[index] = buf; 177 | ass_renderer->cached_src_rects[index] = (SDL_Rect){0, 0, src->w, src->h}; 178 | ass_renderer->cached_dst_rects[index] = (SDL_Rect){src->dst_x, src->dst_y, src->w, src->h}; 179 | index++; 180 | } 181 | 182 | get_cached: 183 | *frames = ass_renderer->cached_items; 184 | *targets = ass_renderer->cached_dst_rects; 185 | *sources = ass_renderer->cached_src_rects; 186 | return ass_renderer->cached_items_size; 187 | } 188 | 189 | static void ren_set_ass_size_cb(Kit_SubtitleRenderer *renderer, int w, int h) { 190 | const Kit_ASSSubtitleRenderer *ass_renderer = renderer->userdata; 191 | SDL_LockMutex(ass_renderer->decoder_lock); 192 | ass_set_frame_size(ass_renderer->renderer, w, h); 193 | SDL_UnlockMutex(ass_renderer->decoder_lock); 194 | } 195 | 196 | Kit_SubtitleRenderer *Kit_CreateASSSubtitleRenderer( 197 | const AVFormatContext *format_ctx, Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h 198 | ) { 199 | assert(dec != NULL); 200 | assert(video_w >= 0); 201 | assert(video_h >= 0); 202 | assert(screen_w >= 0); 203 | assert(screen_h >= 0); 204 | 205 | Kit_SubtitleRenderer *renderer; 206 | Kit_ASSSubtitleRenderer *ass_renderer; 207 | ASS_Renderer *render_handler; 208 | ASS_Track *render_track; 209 | SDL_mutex *decoder_lock; 210 | 211 | const Kit_LibraryState *state = Kit_GetLibraryState(); 212 | if(state->libass_handle == NULL) { 213 | Kit_SetError("Libass library has not been initialized"); 214 | return NULL; 215 | } 216 | if((ass_renderer = calloc(1, sizeof(Kit_ASSSubtitleRenderer))) == NULL) { 217 | Kit_SetError("Unable to allocate ass subtitle renderer"); 218 | goto exit_0; 219 | } 220 | if((renderer = Kit_CreateSubtitleRenderer( 221 | dec, 222 | ren_render_ass_cb, 223 | ren_get_ass_data_cb, 224 | ren_get_ass_raw_frames_cb, 225 | ren_set_ass_size_cb, 226 | NULL, 227 | NULL, 228 | ren_close_ass_cb, 229 | ass_renderer 230 | )) == NULL) { 231 | goto exit_1; 232 | } 233 | if((render_handler = ass_renderer_init(state->libass_handle)) == NULL) { 234 | Kit_SetError("Unable to initialize libass renderer"); 235 | goto exit_2; 236 | } 237 | if((decoder_lock = SDL_CreateMutex()) == NULL) { 238 | Kit_SetError("Unable to initialize libass decoder lock mutex"); 239 | goto exit_3; 240 | } 241 | 242 | // Read fonts from attachment streams and give them to libass 243 | const AVStream *st = NULL; 244 | for(int j = 0; j < format_ctx->nb_streams; j++) { 245 | st = format_ctx->streams[j]; 246 | const AVCodecParameters *codec = st->codecpar; 247 | if(Kit_StreamIsFontAttachment(st)) { 248 | const AVDictionaryEntry *tag = av_dict_get(st->metadata, "filename", NULL, AV_DICT_MATCH_CASE); 249 | if(tag) { 250 | ass_add_font(state->libass_handle, tag->value, (char *)codec->extradata, codec->extradata_size); 251 | } 252 | } 253 | } 254 | 255 | // Init libass fonts and window frame size 256 | ass_set_fonts(render_handler, NULL, "sans-serif", ASS_FONTPROVIDER_AUTODETECT, NULL, 1); 257 | ass_set_storage_size(render_handler, video_w, video_h); 258 | ass_set_frame_size(render_handler, screen_w, screen_h); 259 | ass_set_hinting(render_handler, state->font_hinting); 260 | 261 | if((render_track = ass_new_track(state->libass_handle)) == NULL) { 262 | Kit_SetError("Unable to initialize libass track"); 263 | goto exit_4; 264 | } 265 | if(dec->codec_ctx->subtitle_header) { 266 | ass_process_codec_private( 267 | render_track, (char *)dec->codec_ctx->subtitle_header, dec->codec_ctx->subtitle_header_size 268 | ); 269 | } 270 | 271 | ass_renderer->cached_items = NULL; 272 | ass_renderer->cached_dst_rects = NULL; 273 | ass_renderer->cached_src_rects = NULL; 274 | ass_renderer->cached_items_size = 0; 275 | ass_renderer->renderer = render_handler; 276 | ass_renderer->track = render_track; 277 | ass_renderer->decoder_lock = decoder_lock; 278 | return renderer; 279 | 280 | exit_4: 281 | SDL_DestroyMutex(decoder_lock); 282 | exit_3: 283 | ass_renderer_done(render_handler); 284 | exit_2: 285 | Kit_CloseSubtitleRenderer(renderer); 286 | exit_1: 287 | free(ass_renderer); 288 | exit_0: 289 | return NULL; 290 | } 291 | -------------------------------------------------------------------------------- /src/internal/subtitle/renderers/kitsubimage.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "kitchensink2/internal/kitlibstate.h" 7 | #include "kitchensink2/internal/subtitle/kitatlas.h" 8 | #include "kitchensink2/internal/subtitle/kitsubtitlepacket.h" 9 | #include "kitchensink2/internal/subtitle/renderers/kitsubimage.h" 10 | #include "kitchensink2/kiterror.h" 11 | 12 | typedef struct Kit_ImageSubtitleRenderer { 13 | int video_w; 14 | int video_h; 15 | float scale_x; 16 | float scale_y; 17 | Kit_PacketBuffer *buffer; 18 | Kit_SubtitlePacket *in_packet; 19 | Kit_SubtitlePacket *out_packet; 20 | SDL_Surface **cached_surfaces; 21 | unsigned char **cached_items; 22 | SDL_Rect *cached_src_rects; 23 | SDL_Rect *cached_dst_rects; 24 | unsigned int cached_items_size; 25 | } Kit_ImageSubtitleRenderer; 26 | 27 | static void ren_render_image_cb(Kit_SubtitleRenderer *renderer, void *sub_src, double pts, double start, double end) { 28 | assert(renderer != NULL); 29 | assert(sub_src != NULL); 30 | 31 | Kit_ImageSubtitleRenderer *image_renderer = renderer->userdata; 32 | const AVSubtitle *sub = sub_src; 33 | SDL_Surface *tmp = NULL; 34 | SDL_Surface *dst = NULL; 35 | const double start_pts = pts + start; 36 | const double end_pts = pts + end; 37 | 38 | // If this subtitle has no rects, we still need to clear screen from old subs 39 | if(sub->num_rects == 0) { 40 | Kit_SetSubtitlePacketData(image_renderer->in_packet, true, start_pts, end_pts, 0, 0, NULL); 41 | Kit_WritePacketBuffer(image_renderer->buffer, image_renderer->in_packet); 42 | return; 43 | } 44 | 45 | // Convert subtitle images from paletted to RGBA8888 46 | const AVSubtitleRect *r = NULL; 47 | for(int n = 0; n < sub->num_rects; n++) { 48 | r = sub->rects[n]; 49 | if(r->type != SUBTITLE_BITMAP) 50 | continue; 51 | 52 | tmp = SDL_CreateRGBSurfaceWithFormatFrom(r->data[0], r->w, r->h, 8, r->linesize[0], SDL_PIXELFORMAT_INDEX8); 53 | SDL_SetPaletteColors(tmp->format->palette, (SDL_Color *)r->data[1], 0, 256); 54 | dst = SDL_CreateRGBSurfaceWithFormat(0, r->w, r->h, 32, SDL_PIXELFORMAT_RGBA32); 55 | SDL_BlitSurface(tmp, NULL, dst, NULL); 56 | SDL_FreeSurface(tmp); 57 | 58 | // Create a new packet and write it to output buffer 59 | Kit_SetSubtitlePacketData(image_renderer->in_packet, false, start_pts, end_pts, r->x, r->y, dst); 60 | Kit_WritePacketBuffer(image_renderer->buffer, image_renderer->in_packet); 61 | } 62 | } 63 | 64 | static bool Kit_ProcessPacketToAtlas( 65 | const Kit_ImageSubtitleRenderer *image_renderer, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 66 | ) { 67 | if(image_renderer->out_packet->surface == NULL && !image_renderer->out_packet->clear) { 68 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, true); 69 | return false; 70 | } 71 | if(image_renderer->out_packet->pts_end < current_pts) { 72 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, true); 73 | return false; 74 | } 75 | if(image_renderer->out_packet->clear) 76 | Kit_ClearAtlasContent(atlas); 77 | if(image_renderer->out_packet->surface != NULL) { 78 | SDL_Rect target; 79 | target.x = image_renderer->out_packet->x * image_renderer->scale_x; 80 | target.y = image_renderer->out_packet->y * image_renderer->scale_y; 81 | target.w = image_renderer->out_packet->surface->w * image_renderer->scale_x; 82 | target.h = image_renderer->out_packet->surface->h * image_renderer->scale_y; 83 | Kit_AddAtlasItem(atlas, texture, image_renderer->out_packet->surface, &target); 84 | } 85 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, true); 86 | return true; 87 | } 88 | 89 | static int ren_get_img_data_cb( 90 | Kit_SubtitleRenderer *renderer, Kit_TextureAtlas *atlas, SDL_Texture *texture, double current_pts 91 | ) { 92 | const Kit_ImageSubtitleRenderer *image_renderer = renderer->userdata; 93 | 94 | Kit_CheckAtlasTextureSize(atlas, texture); 95 | Kit_ProcessPacketToAtlas(image_renderer, atlas, texture, current_pts); 96 | while(Kit_ReadPacketBuffer(image_renderer->buffer, image_renderer->out_packet, 0)) 97 | if(!Kit_ProcessPacketToAtlas(image_renderer, atlas, texture, current_pts)) 98 | break; 99 | return 0; 100 | } 101 | 102 | static void Kit_ClearSubCache(Kit_ImageSubtitleRenderer *image_renderer) { 103 | for(int i = 0; i < image_renderer->cached_items_size; i++) { 104 | SDL_FreeSurface(image_renderer->cached_surfaces[i]); 105 | } 106 | image_renderer->cached_items_size = 0; 107 | } 108 | 109 | static bool Kit_ProcessPacketToCache(Kit_ImageSubtitleRenderer *image_renderer, double current_pts) { 110 | if(image_renderer->out_packet->surface == NULL && !image_renderer->out_packet->clear) { 111 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, true); 112 | return false; 113 | } 114 | if(image_renderer->out_packet->pts_end < current_pts) { 115 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, true); 116 | return false; 117 | } 118 | if(image_renderer->out_packet->clear) { 119 | Kit_ClearSubCache(image_renderer); 120 | } 121 | if(image_renderer->out_packet->surface != NULL) { 122 | const unsigned int index = image_renderer->cached_items_size; 123 | image_renderer->cached_items_size++; 124 | 125 | image_renderer->cached_items = 126 | realloc(image_renderer->cached_items, image_renderer->cached_items_size * sizeof(unsigned char *)); 127 | image_renderer->cached_surfaces = 128 | realloc(image_renderer->cached_surfaces, image_renderer->cached_items_size * sizeof(SDL_Surface *)); 129 | image_renderer->cached_dst_rects = 130 | realloc(image_renderer->cached_dst_rects, image_renderer->cached_items_size * sizeof(SDL_Rect)); 131 | image_renderer->cached_src_rects = 132 | realloc(image_renderer->cached_src_rects, image_renderer->cached_items_size * sizeof(SDL_Rect)); 133 | 134 | image_renderer->cached_surfaces[index] = image_renderer->out_packet->surface; 135 | image_renderer->cached_items[index] = image_renderer->out_packet->surface->pixels; 136 | image_renderer->cached_src_rects[index] = (SDL_Rect){ 137 | .x = 0, 138 | .y = 0, 139 | .w = image_renderer->out_packet->surface->w, 140 | .h = image_renderer->out_packet->surface->h, 141 | }; 142 | image_renderer->cached_dst_rects[index] = (SDL_Rect){ 143 | .x = image_renderer->out_packet->x * image_renderer->scale_x, 144 | .y = image_renderer->out_packet->y * image_renderer->scale_y, 145 | .w = image_renderer->out_packet->surface->w * image_renderer->scale_x, 146 | .h = image_renderer->out_packet->surface->h * image_renderer->scale_y, 147 | }; 148 | } 149 | Kit_DelSubtitlePacketRefs(image_renderer->out_packet, false); 150 | return true; 151 | } 152 | 153 | static int ren_get_img_raw_frames_cb( 154 | Kit_SubtitleRenderer *renderer, unsigned char ***frames, SDL_Rect **sources, SDL_Rect **targets, double current_pts 155 | ) { 156 | Kit_ImageSubtitleRenderer *image_renderer = renderer->userdata; 157 | Kit_ProcessPacketToCache(image_renderer, current_pts); 158 | while(Kit_ReadPacketBuffer(image_renderer->buffer, image_renderer->out_packet, 0)) 159 | if(!Kit_ProcessPacketToCache(image_renderer, current_pts)) 160 | break; 161 | 162 | *frames = image_renderer->cached_items; 163 | *targets = image_renderer->cached_dst_rects; 164 | *sources = image_renderer->cached_src_rects; 165 | return image_renderer->cached_items_size; 166 | } 167 | 168 | static void ren_set_img_size_cb(Kit_SubtitleRenderer *ren, int w, int h) { 169 | Kit_ImageSubtitleRenderer *img_ren = ren->userdata; 170 | img_ren->scale_x = (float)w / (float)img_ren->video_w; 171 | img_ren->scale_y = (float)h / (float)img_ren->video_h; 172 | } 173 | 174 | static void ren_flush_cb(Kit_SubtitleRenderer *ren) { 175 | Kit_ImageSubtitleRenderer *image_renderer = ren->userdata; 176 | Kit_FlushPacketBuffer(image_renderer->buffer); 177 | } 178 | 179 | static void ren_signal_cb(Kit_SubtitleRenderer *ren) { 180 | Kit_ImageSubtitleRenderer *image_renderer = ren->userdata; 181 | Kit_SignalPacketBuffer(image_renderer->buffer); 182 | } 183 | 184 | static void ren_close_img_cb(Kit_SubtitleRenderer *renderer) { 185 | if(!renderer || !renderer->userdata) 186 | return; 187 | Kit_ImageSubtitleRenderer *image_renderer = renderer->userdata; 188 | if(image_renderer->buffer) 189 | Kit_FreePacketBuffer(&image_renderer->buffer); 190 | if(image_renderer->in_packet) 191 | Kit_FreeSubtitlePacket(&image_renderer->in_packet); 192 | if(image_renderer->out_packet) 193 | Kit_FreeSubtitlePacket(&image_renderer->out_packet); 194 | Kit_ClearSubCache(image_renderer); 195 | free(image_renderer->cached_items); 196 | free(image_renderer->cached_dst_rects); 197 | free(image_renderer->cached_src_rects); 198 | free(image_renderer->cached_surfaces); 199 | free(image_renderer); 200 | } 201 | 202 | Kit_SubtitleRenderer * 203 | Kit_CreateImageSubtitleRenderer(Kit_Decoder *dec, int video_w, int video_h, int screen_w, int screen_h) { 204 | assert(dec != NULL); 205 | assert(video_w >= 0); 206 | assert(video_h >= 0); 207 | assert(screen_w >= 0); 208 | assert(screen_h >= 0); 209 | 210 | Kit_LibraryState *state = Kit_GetLibraryState(); 211 | Kit_SubtitleRenderer *renderer; 212 | Kit_ImageSubtitleRenderer *image_renderer; 213 | Kit_PacketBuffer *buffer; 214 | Kit_SubtitlePacket *in_packet; 215 | Kit_SubtitlePacket *out_packet; 216 | 217 | if((image_renderer = calloc(1, sizeof(Kit_ImageSubtitleRenderer))) == NULL) { 218 | Kit_SetError("Unable to allocate image subtitle renderer"); 219 | goto exit_0; 220 | } 221 | if((renderer = Kit_CreateSubtitleRenderer( 222 | dec, 223 | ren_render_image_cb, 224 | ren_get_img_data_cb, 225 | ren_get_img_raw_frames_cb, 226 | ren_set_img_size_cb, 227 | ren_flush_cb, 228 | ren_signal_cb, 229 | ren_close_img_cb, 230 | image_renderer 231 | )) == NULL) { 232 | goto exit_1; 233 | } 234 | if((buffer = Kit_CreatePacketBuffer( 235 | state->subtitle_frame_buffer_size, 236 | (buf_obj_alloc)Kit_CreateSubtitlePacket, 237 | (buf_obj_unref)Kit_DelSubtitlePacketRefs, 238 | (buf_obj_free)Kit_FreeSubtitlePacket, 239 | (buf_obj_move)Kit_MoveSubtitlePacketRefs, 240 | (buf_obj_ref)Kit_CreateSubtitlePacketRef 241 | )) == NULL) { 242 | Kit_SetError("Unable to create an output buffer for subtitle renderer"); 243 | goto exit_2; 244 | } 245 | if((in_packet = Kit_CreateSubtitlePacket()) == NULL) { 246 | Kit_SetError("Unable to allocate a input packet for subtitle renderer"); 247 | goto exit_3; 248 | } 249 | if((out_packet = Kit_CreateSubtitlePacket()) == NULL) { 250 | Kit_SetError("Unable to allocate a output packet for subtitle renderer"); 251 | goto exit_4; 252 | } 253 | 254 | // Only renderer required, no other data. 255 | image_renderer->buffer = buffer; 256 | image_renderer->in_packet = in_packet; 257 | image_renderer->out_packet = out_packet; 258 | image_renderer->video_w = video_w; 259 | image_renderer->video_h = video_h; 260 | image_renderer->scale_x = (float)screen_w / (float)video_w; 261 | image_renderer->scale_y = (float)screen_h / (float)video_h; 262 | image_renderer->cached_items = NULL; 263 | image_renderer->cached_surfaces = NULL; 264 | image_renderer->cached_dst_rects = NULL; 265 | image_renderer->cached_src_rects = NULL; 266 | image_renderer->cached_items_size = 0; 267 | return renderer; 268 | 269 | exit_4: 270 | Kit_FreeSubtitlePacket(&in_packet); 271 | exit_3: 272 | Kit_FreePacketBuffer(&buffer); 273 | exit_2: 274 | Kit_CloseSubtitleRenderer(renderer); 275 | exit_1: 276 | free(image_renderer); 277 | exit_0: 278 | return NULL; 279 | } 280 | --------------------------------------------------------------------------------