summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTuomas Virtanen <katajakasa@gmail.com>2016-01-04 04:33:59 +0200
committerTuomas Virtanen <katajakasa@gmail.com>2016-01-04 04:33:59 +0200
commit537ca31915603d7ed47ab4374a74058e340125c7 (patch)
treeb3c19e8dae778385021ea08be596022d952ccab5
Initial commit; Not done yet though, needs more work.
-rw-r--r--.gitignore35
-rw-r--r--CMakeLists.txt73
-rw-r--r--LICENSE22
-rw-r--r--README.md55
-rw-r--r--cmake/FindSDL2.cmake54
-rw-r--r--cmake/Findcunit.cmake41
-rw-r--r--cmake/Findffmpeg.cmake74
-rw-r--r--examples/example_play.c92
-rw-r--r--include/kitchensink/kitchensink.h38
-rw-r--r--include/kitchensink/kiterror.h16
-rw-r--r--include/kitchensink/kitplayer.h23
-rw-r--r--include/kitchensink/kitsource.h51
-rw-r--r--src/kitchensink.c26
-rw-r--r--src/kiterror.c30
-rw-r--r--src/kitplayer.c2
-rw-r--r--src/kitsource.c219
-rw-r--r--tests/CMakeLists.txt18
-rw-r--r--tests/data/CEP140_512kb.mp4bin0 -> 3628135 bytes
-rw-r--r--tests/test_lib.c28
-rw-r--r--tests/test_source.c53
20 files changed, 950 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..14b61e5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+
+# Other
+build/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..e784946
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,73 @@
+cmake_minimum_required(VERSION 2.8)
+project(SDL_kitchensink C)
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
+
+set(VERSION_MAJOR "0")
+set(VERSION_MINOR "0")
+set(VERSION_PATCH "1")
+add_definitions(
+ -DKIT_VERSION_MAJOR=${VERSION_MAJOR}
+ -DKIT_VERSION_MINOR=${VERSION_MINOR}
+ -DKIT_VERSION_PATCH=${VERSION_PATCH}
+)
+
+set(CMAKE_C_FLAGS "-Wall -std=c99")
+set(CMAKE_C_FLAGS_DEBUG "-ggdb -Werror -fno-omit-frame-pointer")
+set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -O2 -fno-omit-frame-pointer -DNDEBUG")
+set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG")
+set(CMAKE_C_FLAGS_MINSIZEREL "-Os -DNDEBUG")
+
+option(BUILD_EXAMPLES "Build examples" OFF)
+option(BUILD_TESTS "Build unittests" OFF)
+
+find_package(SDL2)
+find_package(ffmpeg COMPONENTS avcodec avformat avutil swscale swresample)
+
+if(BUILD_TESTS)
+ add_subdirectory(tests)
+endif()
+
+include_directories(
+ include/
+ ${SDL2_INCLUDE_DIRS}
+ ${FFMPEG_INCLUDE_DIRS}
+)
+
+set(SOURCES
+ src/kitchensink.c
+ src/kiterror.c
+ src/kitplayer.c
+ src/kitsource.c
+)
+
+add_library(SDL_kitchensink SHARED ${SOURCES})
+add_library(SDL_kitchensink_static STATIC ${SOURCES})
+
+set_target_properties(SDL_kitchensink PROPERTIES DEBUG_POSTFIX "d")
+set_target_properties(SDL_kitchensink_static PROPERTIES DEBUG_POSTFIX "d")
+
+target_link_libraries(SDL_kitchensink
+ ${SDL2_LIBRARIES}
+ ${FFMPEG_LIBRARIES}
+)
+
+if(BUILD_EXAMPLES)
+ add_executable(exampleplay examples/example_play.c)
+ if(MINGW)
+ target_link_libraries(exampleplay mingw32)
+ endif()
+ target_link_libraries(exampleplay
+ SDL_kitchensink_static
+ ${SDL2_LIBRARIES}
+ ${FFMPEG_LIBRARIES}
+ )
+endif()
+
+# Installation
+FILE(GLOB H_FILES "include/kitchensink/*.h")
+INSTALL(FILES ${H_FILES} DESTINATION include/kitchensink/)
+INSTALL(TARGETS SDL_kitchensink SDL_kitchensink_static
+ RUNTIME DESTINATION bin
+ LIBRARY DESTINATION lib
+ ARCHIVE DESTINATION lib
+)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bec844e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Tuomas Virtanen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d39f112
--- /dev/null
+++ b/README.md
@@ -0,0 +1,55 @@
+# SDL_kitchensink
+
+FFMPEG and SDL2 based library for audio and video playback.
+
+## 1. Library requirements
+
+Build requirements:
+* CMake (>=2.8)
+* GCC (C99 support required)
+
+Clang might work, but is not tested!
+
+Library requirements:
+* SDL2 (>=2.0.3)
+* FFMPEG (>=2.8)
+* CUnit (optional, for unittests)
+
+Older package versions may or may not work; versions noted here are the only ones tested.
+
+### 1.1. Debian / Ubuntu
+
+```
+sudo apt-get install libsdl2-dev libavcodec-dev libavdevice-dev libavfilter-dev \
+libavformat-dev libavresample-dev libavutil-dev libswresample-dev libswscale-dev \
+libpostproc-dev
+```
+
+### 1.2. MSYS2 64bit
+
+These are for x86_64. For 32bit installation, just change the package names a bit .
+```
+pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-ffmpeg
+
+```
+
+## 2. Compiling
+
+### 2.1. Building the libraries
+
+1. ```cmake -G "MSYS Makefiles" -DCMAKE_BULD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..```
+2. ```make```
+3. ```sudo make install```
+4. Profit!
+
+### 2.2. Building examples
+
+Just add ```-DBUILD_EXAMPLES=1``` to cmake arguments and rebuild.
+
+### 2.3. Building unittests
+
+Make sure CUnit is installed, then add ```-DBUILD_UNITTESTS=1``` to the cmake arguments and rebuild.
+
+## 3. License
+
+MIT. Please see ```LICENSE``` for details.
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
new file mode 100644
index 0000000..c0b43bc
--- /dev/null
+++ b/cmake/FindSDL2.cmake
@@ -0,0 +1,54 @@
+# A Simple SDL2 Finder.
+# (c) Tuomas Virtanen 2016 (Licensed under MIT license)
+# Usage:
+# find_package(SDL2)
+#
+# Declares:
+# * SDL2_FOUND
+# * SDL2_INCLUDE_DIRS
+# * SDL2_LIBRARIES
+#
+
+set(SDL2_SEARCH_PATHS
+ /usr/local/
+ /usr/
+ /opt
+)
+
+find_path(SDL2_INCLUDE_DIR SDL2/SDL.h
+ HINTS
+ PATH_SUFFIXES include
+ PATHS ${SDL2_SEARCH_PATHS}
+)
+
+find_library(SDL2_LIBRARY
+ NAMES SDL2
+ HINTS
+ PATH_SUFFIXES lib
+ PATHS ${SDL2_SEARCH_PATHS}
+)
+
+if(MINGW)
+ find_library(SDL2MAIN_LIBRARY
+ NAMES SDL2main
+ HINTS
+ PATH_SUFFIXES lib
+ PATHS ${SDL2_SEARCH_PATHS}
+ )
+else()
+ SET(SDL2MAIN_LIBRARY "")
+endif()
+
+if(SDL2_INCLUDE_DIR AND SDL2_LIBRARY)
+ SET(SDL2_FOUND TRUE)
+endif()
+
+if(SDL2_FOUND)
+ SET(SDL2_LIBRARIES ${SDL2MAIN_LIBRARY} ${SDL2_LIBRARY})
+ SET(SDL2_INCLUDE_DIRS ${SDL2_INCLUDE_DIR})
+ MESSAGE(STATUS "Found SDL2: ${SDL2_LIBRARIES}")
+else()
+ MESSAGE(WARNING "Could not find SDL2")
+endif()
+
+mark_as_advanced(SDL2MAIN_LIBRARY SDL2_LIBRARY SDL2_INCLUDE_DIR SDL2_SEARCH_PATHS)
diff --git a/cmake/Findcunit.cmake b/cmake/Findcunit.cmake
new file mode 100644
index 0000000..ee982b0
--- /dev/null
+++ b/cmake/Findcunit.cmake
@@ -0,0 +1,41 @@
+# A Simple CUnit Finder.
+# (c) Tuomas Virtanen 2016 (Licensed under MIT license)
+# Usage:
+# find_package(cunit)
+#
+# Declares:
+# * CUNIT_FOUND
+# * CUNIT_INCLUDE_DIRS
+# * CUNIT_LIBRARIES
+#
+
+set(CUNIT_SEARCH_PATHS
+ /usr/local/
+ /usr
+ /opt
+)
+
+find_path(CUNIT_INCLUDE_DIR CUnit/CUnit.h
+ HINTS
+ PATH_SUFFIXES include
+ PATHS ${CUNIT_SEARCH_PATHS}
+)
+find_library(CUNIT_LIBRARY cunit
+ HINTS
+ PATH_SUFFIXES lib
+ PATHS ${CUNIT_SEARCH_PATHS}
+)
+
+if(CUNIT_INCLUDE_DIR AND CUNIT_LIBRARY)
+ set(CUNIT_FOUND TRUE)
+endif()
+
+if(CUNIT_FOUND)
+ SET(CUNIT_LIBRARIES ${CUNIT_LIBRARY})
+ SET(CUNIT_INCLUDE_DIRS ${CUNIT_INCLUDE_DIR})
+ message(STATUS "Found CUnit: ${CUNIT_LIBRARY}")
+else()
+ message(WARNING "Could not find CUnit.")
+endif()
+
+mark_as_advanced(CUNIT_LIBRARY CUNIT_INCLUDE_DIR)
diff --git a/cmake/Findffmpeg.cmake b/cmake/Findffmpeg.cmake
new file mode 100644
index 0000000..40d0114
--- /dev/null
+++ b/cmake/Findffmpeg.cmake
@@ -0,0 +1,74 @@
+# A Simple FFMPEG Finder.
+# (c) Tuomas Virtanen 2016 (Licensed under MIT license)
+# Usage:
+# find_package(ffmpeg COMPONENTS avcodec avutil ...)
+#
+# Declares:
+# * FFMPEG_FOUND
+# * FFMPEG_INCLUDE_DIRS
+# * FFMPEG_LIBRARIES
+#
+# Also declares ${component}_FOUND for each component, eg. avcodec_FOUND etc.
+#
+
+set(FFMPEG_SEARCH_PATHS
+ /usr/local
+ /usr
+ /opt
+)
+
+set(FFMPEG_COMPONENTS
+ avcodec
+ avformat
+ avdevice
+ avfilter
+ avresample
+ avutil
+ swresample
+ swscale
+)
+
+set(FFMPEG_INCLUDE_DIRS)
+set(FFMPEG_LIBRARIES)
+set(FFMPEG_FOUND TRUE)
+
+# Walk through all components declared above, and try to find the ones that have been asked
+foreach(comp ${FFMPEG_COMPONENTS})
+ list(FIND ffmpeg_FIND_COMPONENTS ${comp} _index)
+ if(${_index} GREATER -1)
+ # Component requested, try to look up the library and header for it.
+ find_path(${comp}_INCLUDE_DIR lib${comp}/${comp}.h
+ HINTS
+ PATH_SUFFIXES include
+ PATHS ${FFMPEG_SEARCH_PATHS}
+ )
+ find_library(${comp}_LIBRARY lib${comp}
+ HINTS
+ PATH_SUFFIXES lib
+ PATHS ${FFMPEG_SEARCH_PATHS}
+ )
+
+ # If library and header was found, set proper variables
+ # Otherwise print out a warning!
+ if(${comp}_LIBRARY AND ${comp}_INCLUDE_DIR)
+ set(${comp}_FOUND TRUE)
+ list(APPEND FFMPEG_INCLUDE_DIRS ${${comp}_INCLUDE_DIR})
+ list(APPEND FFMPEG_LIBRARIES ${${comp}_LIBRARY})
+ else()
+ set(FFMPEG_FOUND FALSE)
+ set(${comp}_FOUND FALSE)
+ MESSAGE(WARNING "Could not find component: ${comp}")
+ endif()
+
+ # Mark the temporary variables as hidden in the ui
+ mark_as_advanced(${${comp}_LIBRARY} ${${comp}_INCLUDE_DIR})
+ endif()
+endforeach()
+
+if(FFMPEG_FOUND)
+ MESSAGE(STATUS "Found FFMPEG: ${FFMPEG_LIBRARIES}")
+else()
+ MESSAGE(WARNING "Could not find FFMPEG")
+endif()
+
+mark_as_advanced(FFMPEG_COMPONENTS FFMPEG_SEARCH_PATHS)
diff --git a/examples/example_play.c b/examples/example_play.c
new file mode 100644
index 0000000..d5856c6
--- /dev/null
+++ b/examples/example_play.c
@@ -0,0 +1,92 @@
+#include <kitchensink/kitchensink.h>
+#include <SDL2/SDL.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+/*
+* Note! This example does not do proper error handling etc.
+* It is for example use only!
+*/
+
+const char *stream_types[] = {
+ "KIT_STREAMTYPE_UNKNOWN",
+ "KIT_STREAMTYPE_VIDEO",
+ "KIT_STREAMTYPE_AUDIO",
+ "KIT_STREAMTYPE_DATA",
+ "KIT_STREAMTYPE_SUBTITLE",
+ "KIT_STREAMTYPE_ATTACHMENT"
+};
+
+int main(int argc, char *argv[]) {
+ SDL_Window *window = NULL;
+ SDL_Renderer *renderer = NULL;
+ Kit_Source *src = NULL;
+ SDL_Event event;
+ int err = 0;
+ bool run = false;
+ const char* filename = NULL;
+
+ if(argc != 2) {
+ fprintf(stderr, "Usage: exampleplay <filename>\n");
+ return 0;
+ }
+ filename = argv[1];
+
+ err = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);
+ window = SDL_CreateWindow("Example Player", -1, -1, 1280, 800, SDL_WINDOW_SHOWN);
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC);
+ Kit_Init(KIT_INIT_FORMATS|KIT_INIT_NETWORK);
+
+ if(err != 0 || window == NULL || renderer == NULL) {
+ fprintf(stderr, "Unable to initialize SDL!\n");
+ return 1;
+ }
+
+ // Open up the sourcefile.
+ src = Kit_CreateSourceFromUrl(filename);
+ if(src == NULL) {
+ fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError());
+ return 1;
+ }
+
+ // Print stream information
+ Kit_StreamInfo info;
+ for(int i = 0; i < Kit_GetSourceStreamCount(src); i++) {
+ err = Kit_GetSourceStreamInfo(src, &info, i);
+ if(err) {
+ fprintf(stderr, "Unable to fetch stream #%d information: %s.\n", i, Kit_GetError());
+ return 1;
+ }
+ fprintf(stderr, "Stream #%d: %s\n", i, stream_types[info.type]);
+ }
+
+ // Initialize codecs.
+ // We could choose streams before this if we wanted to; now we just use the defaults (best guesses)
+ if(Kit_InitSourceCodecs(src)) {
+ fprintf(stderr, "Error while initializing codecs: %s", Kit_GetError());
+ return 1;
+ }
+
+ while(run) {
+ while(SDL_PollEvent(&event)) {
+ switch(event.type) {
+ case SDL_KEYUP:
+ if(event.key.keysym.sym == SDLK_ESCAPE) {
+ run = false;
+ }
+ break;
+ case SDL_QUIT:
+ run = false;
+ break;
+ }
+ }
+ }
+
+ Kit_CloseSource(src);
+
+ Kit_Quit();
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ return 0;
+} \ No newline at end of file
diff --git a/include/kitchensink/kitchensink.h b/include/kitchensink/kitchensink.h
new file mode 100644
index 0000000..6ece77c
--- /dev/null
+++ b/include/kitchensink/kitchensink.h
@@ -0,0 +1,38 @@
+#ifndef KITCHENSINK_H
+#define KITCHENSINK_H
+
+#include <SDL2/SDL.h>
+#include "kitchensink/kiterror.h"
+#include "kitchensink/kitsource.h"
+#include "kitchensink/kitplayer.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KIT_VERSION(x) \
+ (x)->major = KIT_VERSION_MAJOR; \
+ (x)->minor = KIT_VERSION_MINOR; \
+ (x)->patch = KIT_VERSION_PATCH
+
+typedef struct Kit_Version {
+ Uint8 major;
+ Uint8 minor;
+ Uint8 patch;
+} Kit_Version;
+
+enum {
+ KIT_INIT_FORMATS = 0x1,
+ KIT_INIT_NETWORK = 0x2,
+};
+
+int Kit_Init();
+void Kit_Quit();
+
+void Kit_GetVersion(Kit_Version *version);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITCHENSINK_H
diff --git a/include/kitchensink/kiterror.h b/include/kitchensink/kiterror.h
new file mode 100644
index 0000000..1b88f89
--- /dev/null
+++ b/include/kitchensink/kiterror.h
@@ -0,0 +1,16 @@
+#ifndef KITERROR_H
+#define KITERROR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char* Kit_GetError();
+void Kit_SetError(const char* fmt, ...);
+void Kit_ClearError();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITERROR_H
diff --git a/include/kitchensink/kitplayer.h b/include/kitchensink/kitplayer.h
new file mode 100644
index 0000000..3c88920
--- /dev/null
+++ b/include/kitchensink/kitplayer.h
@@ -0,0 +1,23 @@
+#ifndef KITPLAYER_H
+#define KITPLAYER_H
+
+#include "kitchensink/kitsource.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Kit_Player {
+
+} Kit_Player;
+
+Kit_Player* Kit_CreatePlayer(Kit_Source *src);
+void Kit_ClosePlayer(Kit_Player *player);
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITPLAYER_H
diff --git a/include/kitchensink/kitsource.h b/include/kitchensink/kitsource.h
new file mode 100644
index 0000000..2b72e88
--- /dev/null
+++ b/include/kitchensink/kitsource.h
@@ -0,0 +1,51 @@
+#ifndef KITSOURCE_H
+#define KITSOURCE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define KIT_CODECNAMESIZE 32
+#define KIT_CODECLONGNAMESIZE 128
+
+typedef enum Kit_streamtype {
+ KIT_STREAMTYPE_UNKNOWN,
+ KIT_STREAMTYPE_VIDEO,
+ KIT_STREAMTYPE_AUDIO,
+ KIT_STREAMTYPE_DATA,
+ KIT_STREAMTYPE_SUBTITLE,
+ KIT_STREAMTYPE_ATTACHMENT
+} Kit_streamtype;
+
+typedef struct Kit_Source {
+ int astream_idx;
+ int vstream_idx;
+ void *format_ctx;
+ void *vcodec_ctx;
+ void *acodec_ctx;
+ void *vcodec;
+ void *acodec;
+} Kit_Source;
+
+typedef struct Kit_Stream {
+ int index;
+ Kit_streamtype type;
+ int width;
+ int height;
+} Kit_StreamInfo;
+
+Kit_Source* Kit_CreateSourceFromUrl(const char *path);
+int Kit_InitSourceCodecs(Kit_Source *src);
+void Kit_CloseSource(Kit_Source *src);
+
+int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_StreamInfo *info, int index);
+int Kit_GetSourceStreamCount(const Kit_Source *src);
+int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_streamtype type);
+int Kit_SetSourceStream(Kit_Source *src, const Kit_streamtype type, int index);
+int Kit_GetSourceStream(const Kit_Source *src, const Kit_streamtype type);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // KITSOURCE_H
diff --git a/src/kitchensink.c b/src/kitchensink.c
new file mode 100644
index 0000000..c2b526f
--- /dev/null
+++ b/src/kitchensink.c
@@ -0,0 +1,26 @@
+#include "kitchensink/kitchensink.h"
+#include <libavformat/avformat.h>
+
+static Uint32 _init_flags = 0;
+
+int Kit_Init(Uint32 flags) {
+ if(flags & KIT_INIT_NETWORK)
+ avformat_network_init();
+ if(flags & KIT_INIT_FORMATS)
+ av_register_all();
+ _init_flags = flags;
+ return 0;
+}
+
+void Kit_Quit() {
+ if(_init_flags & KIT_INIT_NETWORK) {
+ avformat_network_deinit();
+ }
+ _init_flags = 0;
+}
+
+void Kit_GetVersion(Kit_Version *version) {
+ version->major = KIT_VERSION_MAJOR;
+ version->minor = KIT_VERSION_MINOR;
+ version->patch = KIT_VERSION_PATCH;
+}
diff --git a/src/kiterror.c b/src/kiterror.c
new file mode 100644
index 0000000..948fda9
--- /dev/null
+++ b/src/kiterror.c
@@ -0,0 +1,30 @@
+#include "kitchensink/kitchensink.h"
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#define KIT_ERRBUFSIZE 1024
+
+static char _error_available = false;
+static char _error_message[KIT_ERRBUFSIZE] = "\0";
+
+const char* Kit_GetError() {
+ if(_error_available) {
+ _error_available = false;
+ return _error_message;
+ }
+ return NULL;
+}
+
+void Kit_SetError(const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(_error_message, KIT_ERRBUFSIZE, (char*)fmt, args);
+ va_end(args);
+ _error_available = true;
+}
+
+void Kit_ClearError() {
+ _error_message[0] = 0;
+ _error_available = false;
+}
diff --git a/src/kitplayer.c b/src/kitplayer.c
new file mode 100644
index 0000000..704317f
--- /dev/null
+++ b/src/kitplayer.c
@@ -0,0 +1,2 @@
+#include <kitchensink/kitplayer.h>
+
diff --git a/src/kitsource.c b/src/kitsource.c
new file mode 100644
index 0000000..c947ea1
--- /dev/null
+++ b/src/kitsource.c
@@ -0,0 +1,219 @@
+#include "kitchensink/kitsource.h"
+#include "kitchensink/kiterror.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+Kit_Source* Kit_CreateSourceFromUrl(const char *url) {
+ AVFormatContext *format_ctx = NULL;
+
+ if(url == NULL) {
+ Kit_SetError("Source URL must not be NULL");
+ return NULL;
+ }
+
+ // Attempt to open source
+ if(avformat_open_input(&format_ctx, url, NULL, NULL) < 0) {
+ Kit_SetError("Unable to open source Url");
+ goto exit_0;
+ }
+
+ // Fetch stream information. This may potentially take a while.
+ if(avformat_find_stream_info(format_ctx, NULL) < 0) {
+ Kit_SetError("Unable to fetch source information");
+ goto exit_1;
+ }
+
+ // Find best streams for defaults
+ int best_astream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
+ int best_vstream_idx = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
+
+ // Allocate and return a new source
+ // TODO: Check allocation errors
+ Kit_Source *out = calloc(1, sizeof(Kit_Source));
+ out->format_ctx = format_ctx;
+ out->astream_idx = best_astream_idx;
+ out->vstream_idx = best_vstream_idx;
+ return out;
+
+exit_1:
+ avformat_close_input(&format_ctx);
+exit_0:
+ return NULL;
+}
+
+int Kit_InitSourceCodecs(Kit_Source *src) {
+ AVCodecContext *acodec_ctx = NULL;
+ AVCodecContext *vcodec_ctx = NULL;
+ AVCodec *acodec = NULL;
+ AVCodec *vcodec = NULL;
+
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return 1;
+ }
+ if(src->acodec_ctx || src->vcodec_ctx) {
+ Kit_SetError("Source codecs already initialized");
+ return 1;
+ }
+
+ // Make sure indexes seem correct
+ AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx;
+ if(src->astream_idx < 0 || src->astream_idx >= format_ctx->nb_streams) {
+ Kit_SetError("Invalid audio stream index");
+ return 1;
+ }
+ if(src->vstream_idx < 0 || src->vstream_idx >= format_ctx->nb_streams) {
+ Kit_SetError("Invalid video stream index");
+ return 1;
+ }
+
+ // Find video decoder
+ vcodec = avcodec_find_decoder(format_ctx->streams[src->vstream_idx]->codec->codec_id);
+ if(!vcodec) {
+ Kit_SetError("No suitable video decoder found");
+ goto exit_0;
+ }
+
+ // Copy the original video codec context
+ vcodec_ctx = avcodec_alloc_context3(vcodec);
+ if(avcodec_copy_context(vcodec_ctx, format_ctx->streams[src->vstream_idx]->codec) != 0) {
+ Kit_SetError("Unable to copy video codec context");
+ goto exit_0;
+ }
+
+ // Create a video decoder context
+ if(avcodec_open2(vcodec_ctx, vcodec, NULL) < 0) {
+ Kit_SetError("Unable to allocate video codec context");
+ goto exit_1;
+ }
+
+ // Find audio decoder
+ acodec = avcodec_find_decoder(format_ctx->streams[src->astream_idx]->codec->codec_id);
+ if(!acodec) {
+ Kit_SetError("No suitable audio decoder found");
+ goto exit_2;
+ }
+
+ // Copy the original audio codec context
+ acodec_ctx = avcodec_alloc_context3(acodec);
+ if(avcodec_copy_context(acodec_ctx, format_ctx->streams[src->astream_idx]->codec) != 0) {
+ Kit_SetError("Unable to copy audio codec context");
+ goto exit_2;
+ }
+
+ // Create an audio decoder context
+ if(avcodec_open2(acodec_ctx, acodec, NULL) < 0) {
+ Kit_SetError("Unable to allocate audio codec context");
+ goto exit_3;
+ }
+
+ src->acodec = acodec;
+ src->vcodec = vcodec;
+ src->acodec_ctx = acodec_ctx;
+ src->vcodec_ctx = vcodec_ctx;
+ return 0;
+
+exit_3:
+ avcodec_free_context(&acodec_ctx);
+exit_2:
+ avcodec_close(vcodec_ctx);
+exit_1:
+ avcodec_free_context(&vcodec_ctx);
+exit_0:
+ return 1;
+}
+
+void Kit_CloseSource(Kit_Source *src) {
+ if(src == NULL) return;
+ avcodec_close((AVCodecContext*)src->acodec_ctx);
+ avcodec_close((AVCodecContext*)src->vcodec_ctx);
+ avcodec_free_context((AVCodecContext**)&src->acodec_ctx);
+ avcodec_free_context((AVCodecContext**)&src->vcodec_ctx);
+ avformat_close_input((AVFormatContext **)&src->format_ctx);
+ free(src);
+}
+
+int Kit_GetSourceStreamInfo(const Kit_Source *src, Kit_StreamInfo *info, int index) {
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return 1;
+ }
+ AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx;
+ if(index < 0 || index >= format_ctx->nb_streams) {
+ Kit_SetError("Invalid stream index");
+ return 1;
+ }
+
+ AVStream *stream = format_ctx->streams[index];
+ switch(stream->codec->codec_type) {
+ case AVMEDIA_TYPE_UNKNOWN: info->type = KIT_STREAMTYPE_UNKNOWN; break;
+ case AVMEDIA_TYPE_DATA: info->type = KIT_STREAMTYPE_DATA; break;
+ case AVMEDIA_TYPE_VIDEO: info->type = KIT_STREAMTYPE_VIDEO; break;
+ case AVMEDIA_TYPE_AUDIO: info->type = KIT_STREAMTYPE_AUDIO; break;
+ case AVMEDIA_TYPE_SUBTITLE: info->type = KIT_STREAMTYPE_SUBTITLE; break;
+ case AVMEDIA_TYPE_ATTACHMENT: info->type = KIT_STREAMTYPE_ATTACHMENT; break;
+ default:
+ Kit_SetError("Unknown native stream type");
+ return 1;
+ }
+
+ info->index = index;
+ info->width = stream->codec->width;
+ info->height = stream->codec->height;
+ return 0;
+}
+
+int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_streamtype type) {
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return -1;
+ }
+ int avmedia_type = 0;
+ switch(type) {
+ case KIT_STREAMTYPE_VIDEO: avmedia_type = AVMEDIA_TYPE_VIDEO; break;
+ case KIT_STREAMTYPE_AUDIO: avmedia_type = AVMEDIA_TYPE_AUDIO; break;
+ default: return -1;
+ }
+ return av_find_best_stream((AVFormatContext *)src->format_ctx, avmedia_type, -1, -1, NULL, 0);
+}
+
+int Kit_SetSourceStream(Kit_Source *src, const Kit_streamtype type, int index) {
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return 1;
+ }
+ switch(type) {
+ case KIT_STREAMTYPE_AUDIO: src->astream_idx = index; break;
+ case KIT_STREAMTYPE_VIDEO: src->vstream_idx = index; break;
+ default:
+ Kit_SetError("Invalid stream type");
+ return 1;
+ }
+ return 0;
+}
+
+int Kit_GetSourceStream(const Kit_Source *src, const Kit_streamtype type) {
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return -1;
+ }
+ switch(type) {
+ case KIT_STREAMTYPE_AUDIO: return src->astream_idx;
+ case KIT_STREAMTYPE_VIDEO: return src->vstream_idx;
+ default:
+ break;
+ }
+ return -1;
+}
+
+int Kit_GetSourceStreamCount(const Kit_Source *src) {
+ if(src == NULL) {
+ Kit_SetError("Source must not be NULL");
+ return -1;
+ }
+ return ((AVFormatContext *)src->format_ctx)->nb_streams;
+}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000..ddfe4f9
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,18 @@
+find_package(CUnit)
+
+add_executable(test_lib
+ test_lib.c
+ test_source.c
+)
+
+include_directories(${CUNIT_INCLUDE_DIR} . ../include/)
+if(MINGW)
+ target_link_libraries(test_lib mingw32)
+endif()
+target_link_libraries(test_lib
+ SDL_kitchensink_static
+ ${CUNIT_LIBRARIES}
+ ${SDL2_LIBRARIES}
+ ${FFMPEG_LIBRARIES}
+)
+add_custom_target(unittest test_lib)
diff --git a/tests/data/CEP140_512kb.mp4 b/tests/data/CEP140_512kb.mp4
new file mode 100644
index 0000000..4341032
--- /dev/null
+++ b/tests/data/CEP140_512kb.mp4
Binary files differ
diff --git a/tests/test_lib.c b/tests/test_lib.c
new file mode 100644
index 0000000..92eb058
--- /dev/null
+++ b/tests/test_lib.c
@@ -0,0 +1,28 @@
+#include <CUnit/CUnit.h>
+#include <CUnit/Basic.h>
+#include "kitchensink/kitchensink.h"
+
+void source_test_suite(CU_pSuite suite);
+
+int main(int argc, char **argv) {
+ CU_pSuite suite = NULL;
+
+ Kit_Init(KIT_INIT_NETWORK|KIT_INIT_FORMATS);
+
+ if(CU_initialize_registry() != CUE_SUCCESS) {
+ return CU_get_error();
+ }
+
+ suite = CU_add_suite("Source functions", NULL, NULL);
+ if(suite == NULL) goto end;
+ source_test_suite(suite);
+
+ // Run tests
+ CU_basic_set_mode(CU_BRM_VERBOSE);
+ CU_basic_run_tests();
+
+end:
+ CU_cleanup_registry();
+ Kit_Quit();
+ return CU_get_error();
+}
diff --git a/tests/test_source.c b/tests/test_source.c
new file mode 100644
index 0000000..772ebfd
--- /dev/null
+++ b/tests/test_source.c
@@ -0,0 +1,53 @@
+#include <CUnit/CUnit.h>
+#include <CUnit/Basic.h>
+#include <kitchensink/kitchensink.h>
+
+Kit_Source *src = NULL;
+
+void test_Kit_CreateSourceFromUrl(void) {
+ CU_ASSERT_PTR_NULL(Kit_CreateSourceFromUrl(NULL));
+ CU_ASSERT_PTR_NULL(Kit_CreateSourceFromUrl("nonexistent"));
+ src = Kit_CreateSourceFromUrl("../../tests/data/CEP140_512kb.mp4");
+ CU_ASSERT_PTR_NOT_NULL(src);
+}
+
+void test_Kit_GetBestSourceStream(void) {
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO) == 0);
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO) == 1);
+ CU_ASSERT(Kit_GetBestSourceStream(NULL, KIT_STREAMTYPE_AUDIO) == -1);
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_UNKNOWN) == -1);
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_DATA) == -1);
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_ATTACHMENT) == -1);
+ CU_ASSERT(Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE) == -1);
+}
+
+void test_Kit_GetSourceStreamCount(void) {
+ CU_ASSERT(Kit_GetSourceStreamCount(NULL) == -1);
+ CU_ASSERT(Kit_GetSourceStreamCount(src) == 2);
+}
+
+void test_Kit_SetSourceStream(void) {
+ CU_ASSERT(Kit_SetSourceStream(NULL, KIT_STREAMTYPE_VIDEO, 0) == 1);
+ CU_ASSERT(Kit_SetSourceStream(src, KIT_STREAMTYPE_VIDEO, 0) == 0);
+ CU_ASSERT(Kit_SetSourceStream(src, KIT_STREAMTYPE_UNKNOWN, 0) == 1);
+}
+
+void test_Kit_GetSourceStream(void) {
+ CU_ASSERT(Kit_GetSourceStream(NULL, KIT_STREAMTYPE_VIDEO) == -1);
+ CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_VIDEO) == 0);
+ CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_AUDIO) == 1);
+ CU_ASSERT(Kit_GetSourceStream(src, KIT_STREAMTYPE_UNKNOWN) == -1);
+}
+
+void test_Kit_CloseSource(void) {
+ Kit_CloseSource(src);
+}
+
+void source_test_suite(CU_pSuite suite) {
+ if(CU_add_test(suite, "Kit_CreateSourceFromUrl", test_Kit_CreateSourceFromUrl) == NULL) { return; }
+ if(CU_add_test(suite, "Kit_GetBestSourceStream", test_Kit_GetBestSourceStream) == NULL) { return; }
+ if(CU_add_test(suite, "Kit_GetSourceStreamCount", test_Kit_GetSourceStreamCount) == NULL) { return; }
+ if(CU_add_test(suite, "Kit_SetSourceStream", test_Kit_SetSourceStream) == NULL) { return; }
+ if(CU_add_test(suite, "Kit_GetSourceStream", test_Kit_GetSourceStream) == NULL) { return; }
+ if(CU_add_test(suite, "Kit_CloseSource", test_Kit_CloseSource) == NULL) { return; }
+}