diff options
author | Tuomas Virtanen <katajakasa@gmail.com> | 2018-07-02 10:34:09 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-02 10:34:09 +0300 |
commit | 7aec12156a3dc3df570855cb1c3fa03792f77030 (patch) | |
tree | 6b1d66e504982e24dcf79c710126ce96457c75ef | |
parent | f602daa42a2a9135daa9b9a57e177edfb0c767ba (diff) | |
parent | 5973307722d4531dd24ff7ada8ab11111f5ee378 (diff) |
Merge pull request #42 from katajakasa/source-cleanups
Source cleanups + RWOps
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | examples/example_custom.c | 18 | ||||
-rw-r--r-- | examples/example_rwops.c | 223 | ||||
-rw-r--r-- | include/kitchensink/kitsource.h | 42 | ||||
-rw-r--r-- | src/kitsource.c | 75 |
5 files changed, 336 insertions, 26 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a822008..023692f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,23 +90,27 @@ if(BUILD_EXAMPLES) add_executable(complex examples/example_complex.c) add_executable(simple examples/example_simple.c) add_executable(custom examples/example_custom.c) + add_executable(rwops examples/example_rwops.c) if(MINGW) target_link_libraries(audio mingw32) target_link_libraries(complex mingw32) target_link_libraries(simple mingw32) target_link_libraries(custom mingw32) + target_link_libraries(rwops mingw32) endif() set_property(TARGET audio PROPERTY C_STANDARD 99) set_property(TARGET complex PROPERTY C_STANDARD 99) set_property(TARGET simple PROPERTY C_STANDARD 99) set_property(TARGET custom PROPERTY C_STANDARD 99) + set_property(TARGET rwops PROPERTY C_STANDARD 99) target_link_libraries(audio SDL_kitchensink_static ${LIBRARIES}) target_link_libraries(complex SDL_kitchensink_static ${LIBRARIES}) target_link_libraries(simple SDL_kitchensink_static ${LIBRARIES}) target_link_libraries(custom SDL_kitchensink_static ${LIBRARIES}) + target_link_libraries(rwops SDL_kitchensink_static ${LIBRARIES}) endif() # documentation target diff --git a/examples/example_custom.c b/examples/example_custom.c index 43ae40a..e365850 100644 --- a/examples/example_custom.c +++ b/examples/example_custom.c @@ -22,14 +22,6 @@ int read_callback(void *userdata, uint8_t *buf, int buf_size) { return -1; } -int64_t seek_callback(void *userdata, int64_t offset, int whence) { - FILE *fd = (FILE*)userdata; - if(fseek(fd, offset, whence)) { - return -1; - } - return ftell(fd); -} - int main(int argc, char *argv[]) { int err = 0, ret = 0; const char* filename = NULL; @@ -83,8 +75,8 @@ int main(int argc, char *argv[]) { return 1; } - // Open up the custom source. Declare read & seek callbacks, and transport FD in userdata. - src = Kit_CreateSourceFromCustom(read_callback, seek_callback, fd); + // Open up the custom source. Declare read callback, and transport FD in userdata. + src = Kit_CreateSourceFromCustom(read_callback, NULL, fd); if(src == NULL) { fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); return 1; @@ -171,6 +163,12 @@ int main(int argc, char *argv[]) { case SDL_QUIT: run = false; break; + case SDL_KEYUP: + if(event.key.keysym.sym == SDLK_RIGHT) + Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10); + if(event.key.keysym.sym == SDLK_LEFT) + Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10); + break; } } diff --git a/examples/example_rwops.c b/examples/example_rwops.c new file mode 100644 index 0000000..418f7a7 --- /dev/null +++ b/examples/example_rwops.c @@ -0,0 +1,223 @@ +#include <kitchensink/kitchensink.h> +#include <SDL.h> +#include <stdio.h> +#include <stdbool.h> + +/* +* Note! This example does not do proper error handling etc. +* It is for example use only! +*/ + +#define AUDIOBUFFER_SIZE (1024 * 64) +#define ATLAS_WIDTH 4096 +#define ATLAS_HEIGHT 4096 +#define ATLAS_MAX 1024 + + +int main(int argc, char *argv[]) { + int err = 0, ret = 0; + const char* filename = NULL; + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + bool run = true; + Kit_Source *src = NULL; + Kit_Player *player = NULL; + SDL_AudioSpec wanted_spec, audio_spec; + SDL_AudioDeviceID audio_dev; + + // Get filename to open + if(argc != 2) { + fprintf(stderr, "Usage: custom <filename>\n"); + return 0; + } + filename = argv[1]; + + // Init SDL + err = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO); + if(err != 0) { + fprintf(stderr, "Unable to initialize SDL2!\n"); + return 1; + } + + // Create a resizable window. + window = SDL_CreateWindow("Example Player", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_RESIZABLE); + if(window == NULL) { + fprintf(stderr, "Unable to create a new window!\n"); + return 1; + } + + // Create an accelerated renderer. Enable vsync, so we don't need to play around with SDL_Delay. + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC); + if(window == NULL) { + fprintf(stderr, "Unable to create a renderer!\n"); + return 1; + } + + // Initialize Kitchensink with network and libass support. + err = Kit_Init(KIT_INIT_NETWORK|KIT_INIT_ASS); + if(err != 0) { + fprintf(stderr, "Unable to initialize Kitchensink: %s", Kit_GetError()); + return 1; + } + + // Open file with fopen. We then proceed to read this with our custom file handlers. + SDL_RWops *rw_ops = SDL_RWFromFile(filename, "rb"); + if(rw_ops == NULL) { + fprintf(stderr, "Unable to open file '%s' for reading\n", filename); + return 1; + } + + // Open up the SDL RWops source + src = Kit_CreateSourceFromRW(rw_ops); + if(src == NULL) { + fprintf(stderr, "Unable to load file '%s': %s\n", filename, Kit_GetError()); + return 1; + } + + // Create the player. Pick best video, audio and subtitle streams, and set subtitle + // rendering resolution to screen resolution. + player = Kit_CreatePlayer( + src, + Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO), + Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO), + Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE), + 1280, 720); + if(player == NULL) { + fprintf(stderr, "Unable to create player: %s\n", Kit_GetError()); + return 1; + } + + // Print some information + Kit_PlayerInfo pinfo; + Kit_GetPlayerInfo(player, &pinfo); + + // Make sure there is video in the file to play first. + if(Kit_GetPlayerVideoStream(player) == -1) { + fprintf(stderr, "File contains no video!\n"); + return 1; + } + + // Init audio + SDL_memset(&wanted_spec, 0, sizeof(wanted_spec)); + wanted_spec.freq = pinfo.audio.output.samplerate; + wanted_spec.format = pinfo.audio.output.format; + wanted_spec.channels = pinfo.audio.output.channels; + audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_spec, 0); + SDL_PauseAudioDevice(audio_dev, 0); + + // Initialize video texture. This will probably end up as YV12 most of the time. + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); + SDL_Texture *video_tex = SDL_CreateTexture( + renderer, + pinfo.video.output.format, + SDL_TEXTUREACCESS_STATIC, + pinfo.video.output.width, + pinfo.video.output.height); + if(video_tex == NULL) { + fprintf(stderr, "Error while attempting to create a video texture\n"); + return 1; + } + + // This is the subtitle texture atlas. This contains all the subtitle image fragments. + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "nearest"); // Always nearest for atlas operations + SDL_Texture *subtitle_tex = SDL_CreateTexture( + renderer, + pinfo.subtitle.output.format, + SDL_TEXTUREACCESS_STATIC, + ATLAS_WIDTH, ATLAS_HEIGHT); + if(subtitle_tex == NULL) { + fprintf(stderr, "Error while attempting to create a subtitle texture atlas\n"); + return 1; + } + + // Make sure subtitle texture is in correct blendmode + SDL_SetTextureBlendMode(subtitle_tex, SDL_BLENDMODE_BLEND); + + // Start playback + Kit_PlayerPlay(player); + + // Playback temporary data buffers + char audiobuf[AUDIOBUFFER_SIZE]; + SDL_Rect sources[ATLAS_MAX]; + SDL_Rect targets[ATLAS_MAX]; + + // Get movie area size + SDL_RenderSetLogicalSize(renderer, pinfo.video.output.width, pinfo.video.output.height); + while(run) { + if(Kit_GetPlayerState(player) == KIT_STOPPED) { + run = false; + continue; + } + + SDL_Event event; + while(SDL_PollEvent(&event)) { + switch(event.type) { + case SDL_QUIT: + run = false; + break; + case SDL_KEYUP: + if(event.key.keysym.sym == SDLK_RIGHT) + Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) + 10); + if(event.key.keysym.sym == SDLK_LEFT) + Kit_PlayerSeek(player, Kit_GetPlayerPosition(player) - 10); + break; + + } + } + + // Refresh audio + int queued = SDL_GetQueuedAudioSize(audio_dev); + if(queued < AUDIOBUFFER_SIZE) { + int need = AUDIOBUFFER_SIZE - queued; + + while(need > 0) { + ret = Kit_GetPlayerAudioData( + player, + (unsigned char*)audiobuf, + AUDIOBUFFER_SIZE); + need -= ret; + if(ret > 0) { + SDL_QueueAudio(audio_dev, audiobuf, ret); + } else { + break; + } + } + // If we now have data, start playback (again) + if(SDL_GetQueuedAudioSize(audio_dev) > 0) { + SDL_PauseAudioDevice(audio_dev, 0); + } + } + + // Clear screen with black + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + // Refresh videotexture and render it + Kit_GetPlayerVideoData(player, video_tex); + SDL_RenderCopy(renderer, video_tex, NULL, NULL); + + // Refresh subtitle texture atlas and render subtitle frames from it + // For subtitles, use screen size instead of video size for best quality + int got = Kit_GetPlayerSubtitleData(player, subtitle_tex, sources, targets, ATLAS_MAX); + for(int i = 0; i < got; i++) { + SDL_RenderCopy(renderer, subtitle_tex, &sources[i], &targets[i]); + } + + // Render to screen + wait for vsync + SDL_RenderPresent(renderer); + } + + Kit_ClosePlayer(player); + Kit_CloseSource(src); + SDL_RWclose(rw_ops); + Kit_Quit(); + + SDL_DestroyTexture(subtitle_tex); + SDL_DestroyTexture(video_tex); + SDL_CloseAudioDevice(audio_dev); + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 0; +} diff --git a/include/kitchensink/kitsource.h b/include/kitchensink/kitsource.h index a9da78e..9b744ca 100644 --- a/include/kitchensink/kitsource.h +++ b/include/kitchensink/kitsource.h @@ -10,6 +10,7 @@ */ #include <inttypes.h> +#include <SDL_rwops.h> #include "kitchensink/kitconfig.h" #ifdef __cplusplus @@ -64,7 +65,7 @@ typedef struct Kit_SourceStreamInfo { * - buf, a buffer the data must be copied into * - size, how much data you are expected to provide at maximum. * - * The function must return the amount of bytes copied to the buffer. + * The function must return the amount of bytes copied to the buffer or <0 on error. * * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation * for any further details. @@ -81,7 +82,15 @@ typedef int (*Kit_ReadCallback)(void *userdata, uint8_t *buf, int size); * - offset, an seeking offset in bytes * - whence, reference position for the offset. * - * The function must return the position (in bytes) we seeked to. + * Whence parameter can be one of the standard fseek values or optionally AVSEEK_SIZE. + * - SEEK_SET: Reference position is beginning of file + * - SEEK_CUR: Reference position is the current position of the file pointer + * - SEEK_END: Reference position is the end of the file + * - AVSEEK_SIZE: Optional. Does not seek, instead finds the size of the source file. + * - AVSEEK_FORCE: Optional. Suggests that seeking should be done at any cost. May be passed alongside + * any of the SEEK_* flags, eg. SEEK_SET|AVSEEK_FORCE. + * + * The function must return the position (in bytes) we seeked to or <0 on error or on unsupported operation. * * Note that this callback is passed directly to ffmpeg avio, so please refer to ffmpeg documentation * for any further details. @@ -124,7 +133,7 @@ KIT_API Kit_Source* Kit_CreateSourceFromUrl(const char *url); * * For example: * ``` - * if(Kit_CreateSourceFromUrl(filename) == NULL) { + * if(Kit_CreateSourceFromCustom(read_fn, seek_fn, fp) == NULL) { * fprintf(stderr, "Error: %s\n", Kit_GetError()); * return 1; * } @@ -138,6 +147,33 @@ KIT_API Kit_Source* Kit_CreateSourceFromUrl(const char *url); KIT_API Kit_Source* Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallback seek_cb, void *userdata); /** + * @brief Create a new source from SDL RWops struct + * + * Can be used to read data from SDL compatible sources. + * + * This function will return an initialized Kit_Source on success. Note that you need to manually + * free the source when it's no longer needed by calling Kit_CloseSource(). + * + * On failure, this function will return NULL, and further error data is available via Kit_GetError(). + * + * Note that the RWops struct must exist during the whole lifetime of the source, and you must take + * care of freeing the rwops after it's no longer needed. + * + * For example: + * ``` + * SDL_RWops *rw = SDL_RWFromFile("myvideo.mkv", "rb"); + * if(Kit_CreateSourceFromRW(rw) == NULL) { + * fprintf(stderr, "Error: %s\n", Kit_GetError()); + * return 1; + * } + * ``` + * + * @param rw_ops Initialized RWOps + * @return KIT_API* Kit_CreateSourceFromRW + */ +KIT_API Kit_Source* Kit_CreateSourceFromRW(SDL_RWops *rw_ops); + +/** * @brief Closes a previously initialized source * * Closes a Kit_Source that was previously created by Kit_CreateSourceFromUrl() or Kit_CreateSourceFromCustom() diff --git a/src/kitsource.c b/src/kitsource.c index b13cde6..71a73b0 100644 --- a/src/kitsource.c +++ b/src/kitsource.c @@ -12,6 +12,16 @@ #define AVIO_BUF_SIZE 32768 +static int _ScanSource(AVFormatContext *format_ctx) { + av_opt_set_int(format_ctx, "probesize", INT_MAX, 0); + av_opt_set_int(format_ctx, "analyzeduration", INT_MAX, 0); + if(avformat_find_stream_info(format_ctx, NULL) < 0) { + Kit_SetError("Unable to fetch source information"); + return 1; + } + return 0; +} + Kit_Source* Kit_CreateSourceFromUrl(const char *url) { assert(url != NULL); @@ -27,14 +37,11 @@ Kit_Source* Kit_CreateSourceFromUrl(const char *url) { goto exit_0; } - av_opt_set_int(src->format_ctx, "probesize", INT_MAX, 0); - av_opt_set_int(src->format_ctx, "analyzeduration", INT_MAX, 0); - - // Fetch stream information. This may potentially take a while. - if(avformat_find_stream_info((AVFormatContext *)src->format_ctx, NULL) < 0) { - Kit_SetError("Unable to fetch source information"); + // Scan source information (may seek forwards) + if(_ScanSource(src->format_ctx)) { goto exit_1; } + return src; exit_1: @@ -81,13 +88,8 @@ Kit_Source* Kit_CreateSourceFromCustom(Kit_ReadCallback read_cb, Kit_SeekCallbac goto exit_3; } - // Set probe opts for input scanning - av_opt_set_int(format_ctx, "probesize", INT_MAX, 0); - av_opt_set_int(format_ctx, "analyzeduration", INT_MAX, 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"); + // Scan source information (may seek forwards) + if(_ScanSource(format_ctx)) { goto exit_4; } @@ -109,6 +111,53 @@ exit_0: return NULL; } +static int _RWReadCallback(void *userdata, uint8_t *buf, int size) { + return SDL_RWread((SDL_RWops*)userdata, buf, 1, size); +} + +static int64_t _RWGetSize(SDL_RWops *rw_ops) { + int64_t current_pos; + int64_t max_pos; + + // First, see if tell works at all, and fail with -1 if it doesn't. + current_pos = SDL_RWtell(rw_ops); + if(current_pos < 0) { + return -1; + } + + // Seek to end, get pos (this is the size), then return. + if(SDL_RWseek(rw_ops, 0, RW_SEEK_END) < 0) { + return -1; // Seek failed, never mind then + } + max_pos = SDL_RWtell(rw_ops); + SDL_RWseek(rw_ops, current_pos, RW_SEEK_SET); + return max_pos; +} + +static int64_t _RWSeekCallback(void *userdata, int64_t offset, int whence) { + int rw_whence = 0; + + if(whence & AVSEEK_SIZE) + return _RWGetSize(userdata); + if((whence & ~AVSEEK_FORCE) == SEEK_CUR) + rw_whence = RW_SEEK_CUR; + if((whence & ~AVSEEK_FORCE) == SEEK_SET) + rw_whence = RW_SEEK_SET; + if((whence & ~AVSEEK_FORCE) == SEEK_END) + rw_whence = RW_SEEK_END; + + if(SDL_RWseek((SDL_RWops*)userdata, offset, rw_whence) < 0) { + return -1; + } + + return SDL_RWtell((SDL_RWops*)userdata); +} + + +Kit_Source* Kit_CreateSourceFromRW(SDL_RWops *rw_ops) { + return Kit_CreateSourceFromCustom(_RWReadCallback, _RWSeekCallback, rw_ops); +} + void Kit_CloseSource(Kit_Source *src) { assert(src != NULL); AVFormatContext *format_ctx = src->format_ctx; |