From 2267d51d2b402c377a21bf03be6fc51b251f063b Mon Sep 17 00:00:00 2001 From: Tuomas Virtanen Date: Fri, 22 Sep 2017 20:58:25 +0300 Subject: Decoder cleanups --- CMakeLists.txt | 8 +- include/kitchensink/internal/kitdecoder.h | 3 +- src/internal/kitaudio.c | 299 ++++++++++++++++++++ src/internal/kitbuffer.c | 83 ++++++ src/internal/kitdecoder.c | 219 +++++++++++++++ src/internal/kithelpers.c | 30 ++ src/internal/kitlibstate.c | 7 + src/internal/kitlist.c | 79 ++++++ src/internal/kitringbuffer.c | 172 ++++++++++++ src/internal/kitsubtitle.c | 445 +++++++++++++++++++++++++++++ src/internal/kitvideo.c | 275 ++++++++++++++++++ src/kitaudio.c | 306 -------------------- src/kitbuffer.c | 83 ------ src/kitdecoder.c | 220 --------------- src/kithelpers.c | 30 -- src/kitlibstate.c | 7 - src/kitlist.c | 79 ------ src/kitringbuffer.c | 172 ------------ src/kitsubtitle.c | 446 ------------------------------ src/kitvideo.c | 276 ------------------ 20 files changed, 1614 insertions(+), 1625 deletions(-) create mode 100644 src/internal/kitaudio.c create mode 100644 src/internal/kitbuffer.c create mode 100644 src/internal/kitdecoder.c create mode 100644 src/internal/kithelpers.c create mode 100644 src/internal/kitlibstate.c create mode 100644 src/internal/kitlist.c create mode 100644 src/internal/kitringbuffer.c create mode 100644 src/internal/kitsubtitle.c create mode 100644 src/internal/kitvideo.c delete mode 100644 src/kitaudio.c delete mode 100644 src/kitbuffer.c delete mode 100644 src/kitdecoder.c delete mode 100644 src/kithelpers.c delete mode 100644 src/kitlibstate.c delete mode 100644 src/kitlist.c delete mode 100644 src/kitringbuffer.c delete mode 100644 src/kitsubtitle.c delete mode 100644 src/kitvideo.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 63a600d..d70cfd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.1) project(SDL_kitchensink C) include(GNUInstallDirs) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) @@ -47,8 +47,8 @@ if(USE_ASS) set(INCLUDES ${INCLUDES} ${ASS_INCLUDE_DIRS}) endif() -FILE(GLOB SOURCES "src/*.c") -FILE(GLOB HEADERS "include/kitchensink/*.h") +FILE(GLOB_RECURSE SOURCES "src/*.c") +FILE(GLOB INSTALL_HEADERS "include/kitchensink/*.h") add_library(SDL_kitchensink SHARED ${SOURCES}) add_library(SDL_kitchensink_static STATIC ${SOURCES}) @@ -100,7 +100,7 @@ endif() # Installation install(FILES ${PKG_CONFIG_FILE} DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -install(FILES ${HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/kitchensink) +install(FILES ${INSTALL_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/kitchensink) INSTALL(TARGETS SDL_kitchensink SDL_kitchensink_static RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/include/kitchensink/internal/kitdecoder.h b/include/kitchensink/internal/kitdecoder.h index 9d6f84d..191113b 100644 --- a/include/kitchensink/internal/kitdecoder.h +++ b/include/kitchensink/internal/kitdecoder.h @@ -37,8 +37,7 @@ KIT_LOCAL struct Kit_Decoder { }; KIT_LOCAL Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index, - int in_b_size, int out_b_size, - dec_free_packet_cb free_out_cb); + int out_b_size, dec_free_packet_cb free_out_cb); KIT_LOCAL void Kit_SetDecoderClockSync(Kit_Decoder *dec, double sync); KIT_LOCAL void Kit_ChangeDecoderClockSync(Kit_Decoder *dec, double sync); KIT_LOCAL bool Kit_CanWriteDecoderInput(Kit_Decoder *dec); diff --git a/src/internal/kitaudio.c b/src/internal/kitaudio.c new file mode 100644 index 0000000..4e67b74 --- /dev/null +++ b/src/internal/kitaudio.c @@ -0,0 +1,299 @@ +#include +#define __STDC_FORMAT_MACROS +#include + +#include +#include +#include +#include + +#include "kitchensink/kiterror.h" +#include "kitchensink/internal/kithelpers.h" +#include "kitchensink/internal/kitbuffer.h" +#include "kitchensink/internal/kitaudio.h" +#include "kitchensink/internal/kitringbuffer.h" +#include "kitchensink/internal/kitlog.h" + +#define KIT_AUDIO_OUT_SIZE 16 +#define AUDIO_SYNC_THRESHOLD 0.05 + +typedef struct Kit_AudioDecoder { + Kit_AudioFormat *format; + SwrContext *swr; + AVFrame *scratch_frame; +} Kit_AudioDecoder; + +typedef struct Kit_AudioPacket { + double pts; + size_t original_size; + Kit_RingBuffer *rb; +} Kit_AudioPacket; + + +Kit_AudioPacket* _CreateAudioPacket(const char* data, size_t len, double pts) { + Kit_AudioPacket *p = calloc(1, sizeof(Kit_AudioPacket)); + p->rb = Kit_CreateRingBuffer(len); + Kit_WriteRingBuffer(p->rb, data, len); + p->pts = pts; + return p; +} + +enum AVSampleFormat _FindAVSampleFormat(int format) { + switch(format) { + case AUDIO_U8: return AV_SAMPLE_FMT_U8; + case AUDIO_S16SYS: return AV_SAMPLE_FMT_S16; + case AUDIO_S32SYS: return AV_SAMPLE_FMT_S32; + default: + return AV_SAMPLE_FMT_NONE; + } +} + +int64_t _FindAVChannelLayout(int channels) { + switch(channels) { + case 1: return AV_CH_LAYOUT_MONO; + case 2: return AV_CH_LAYOUT_STEREO; + case 4: return AV_CH_LAYOUT_QUAD; + case 6: return AV_CH_LAYOUT_5POINT1; + default: return AV_CH_LAYOUT_STEREO_DOWNMIX; + } +} + +void _FindAudioFormat(enum AVSampleFormat fmt, int *bytes, bool *is_signed, unsigned int *format) { + switch(fmt) { + case AV_SAMPLE_FMT_U8: + *bytes = 1; + *is_signed = false; + *format = AUDIO_U8; + break; + case AV_SAMPLE_FMT_S16: + *bytes = 2; + *is_signed = true; + *format = AUDIO_S16SYS; + break; + case AV_SAMPLE_FMT_S32: + *bytes = 4; + *is_signed = true; + *format = AUDIO_S32SYS; + break; + default: + *bytes = 2; + *is_signed = true; + *format = AUDIO_S16SYS; + break; + } +} + +static void free_out_audio_packet_cb(void *packet) { + Kit_AudioPacket *p = packet; + Kit_DestroyRingBuffer(p->rb); + free(p); +} + +static int dec_decode_audio_cb(Kit_Decoder *dec, AVPacket *in_packet) { + assert(dec != NULL); + assert(in_packet != NULL); + + Kit_AudioDecoder *audio_dec = dec->userdata; + int frame_finished; + int len, len2; + int dst_linesize; + int dst_nb_samples, dst_bufsize; + unsigned char **dst_data; + + // Decode as long as there is data + while(in_packet->size > 0) { + len = avcodec_decode_audio4(dec->codec_ctx, audio_dec->scratch_frame, &frame_finished, in_packet); + if(len < 0) { + return 1; + } + + if(frame_finished) { + dst_nb_samples = av_rescale_rnd( + audio_dec->scratch_frame->nb_samples, + audio_dec->format->samplerate, // Target samplerate + dec->codec_ctx->sample_rate, // Source samplerate + AV_ROUND_UP); + + av_samples_alloc_array_and_samples( + &dst_data, + &dst_linesize, + audio_dec->format->channels, + dst_nb_samples, + _FindAVSampleFormat(audio_dec->format->format), + 0); + + len2 = swr_convert( + audio_dec->swr, + dst_data, + audio_dec->scratch_frame->nb_samples, + (const unsigned char **)audio_dec->scratch_frame->extended_data, + audio_dec->scratch_frame->nb_samples); + + dst_bufsize = av_samples_get_buffer_size( + &dst_linesize, + audio_dec->format->channels, + len2, + _FindAVSampleFormat(audio_dec->format->format), 1); + + // Get pts for the packet + double pts = 0; + if(in_packet->pts != AV_NOPTS_VALUE) { + pts = av_frame_get_best_effort_timestamp(audio_dec->scratch_frame); + pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); + } + + // Lock, write to audio buffer, unlock + Kit_AudioPacket *out_packet = _CreateAudioPacket( + (char*)dst_data[0], (size_t)dst_bufsize, pts); + Kit_WriteDecoderOutput(dec, out_packet); + + // Free temps + av_freep(&dst_data[0]); + av_freep(&dst_data); + } + + in_packet->size -= len; + in_packet->data += len; + } + + + return 1; +} + +static void dec_close_audio_cb(Kit_Decoder *dec) { + if(dec == NULL) return; + + Kit_AudioDecoder *audio_dec = dec->userdata; + if(audio_dec->scratch_frame != NULL) { + av_frame_free(&audio_dec->scratch_frame); + } + if(audio_dec->swr != NULL) { + swr_free(&audio_dec->swr); + } + free(audio_dec); +} + +Kit_Decoder* Kit_CreateAudioDecoder(const Kit_Source *src, Kit_AudioFormat *format) { + assert(src != NULL); + assert(format != NULL); + if(src->audio_stream_index < 0) { + return NULL; + } + + // First the generic decoder component ... + Kit_Decoder *dec = Kit_CreateDecoder( + src, src->audio_stream_index, + KIT_AUDIO_OUT_SIZE, + free_out_audio_packet_cb); + if(dec == NULL) { + goto exit_0; + } + + // Find formats + format->samplerate = dec->codec_ctx->sample_rate; + format->channels = dec->codec_ctx->channels > 2 ? 2 : dec->codec_ctx->channels; + format->is_enabled = true; + format->stream_index = src->audio_stream_index; + _FindAudioFormat(dec->codec_ctx->sample_fmt, &format->bytes, &format->is_signed, &format->format); + + // ... then allocate the audio decoder + Kit_AudioDecoder *audio_dec = calloc(1, sizeof(Kit_AudioDecoder)); + if(audio_dec == NULL) { + goto exit_1; + } + + // Create temporary audio frame + audio_dec->scratch_frame = av_frame_alloc(); + if(audio_dec->scratch_frame == NULL) { + Kit_SetError("Unable to initialize temporary audio frame"); + goto exit_2; + } + + // Create resampler + audio_dec->swr = swr_alloc_set_opts( + NULL, + _FindAVChannelLayout(format->channels), // Target channel layout + _FindAVSampleFormat(format->format), // Target fmt + format->samplerate, // Target samplerate + dec->codec_ctx->channel_layout, // Source channel layout + dec->codec_ctx->sample_fmt, // Source fmt + dec->codec_ctx->sample_rate, // Source samplerate + 0, NULL); + + if(swr_init(audio_dec->swr) != 0) { + Kit_SetError("Unable to initialize audio resampler context"); + goto exit_3; + } + + // Set callbacks and userdata, and we're go + audio_dec->format = format; + dec->dec_decode = dec_decode_audio_cb; + dec->dec_close = dec_close_audio_cb; + dec->userdata = audio_dec; + return dec; + +exit_3: + av_frame_free(&audio_dec->scratch_frame); +exit_2: + free(audio_dec); +exit_1: + Kit_CloseDecoder(dec); +exit_0: + return NULL; +} + +int Kit_GetAudioDecoderData(Kit_Decoder *dec, unsigned char *buf, int len) { + assert(dec != NULL); + + Kit_AudioPacket *packet = Kit_PeekDecoderOutput(dec); + if(packet == NULL) { + return 0; + } + + int ret = 0; + Kit_AudioDecoder *audio_dec = dec->userdata; + int bytes_per_sample = audio_dec->format->bytes * audio_dec->format->channels; + double bytes_per_second = bytes_per_sample * audio_dec->format->samplerate; + double sync_ts = _GetSystemTime() - dec->clock_sync; + + if(packet->pts > sync_ts + AUDIO_SYNC_THRESHOLD) { + return 0; + } else if(packet->pts < sync_ts - AUDIO_SYNC_THRESHOLD) { + // Audio is lagging, skip until good pts is found + while(1) { + Kit_AdvanceDecoderOutput(dec); + free_out_audio_packet_cb(packet); + packet = Kit_PeekDecoderOutput(dec); + if(packet == NULL) { + break; + } else { + dec->clock_pos = packet->pts; + } + if(packet->pts > sync_ts - AUDIO_SYNC_THRESHOLD) { + break; + } + } + } + + // If we have no viable packet, just skip + if(packet == NULL) { + return 0; + } + + // Read data from packet ringbuffer + if(len > 0) { + ret = Kit_ReadRingBuffer(packet->rb, (char*)buf, len); + } + + // If ringbuffer is cleared, kill packet and advance buffer. + // Otherwise forward the pts value for the current packet. + if(Kit_GetRingBufferLength(packet->rb) == 0) { + Kit_AdvanceDecoderOutput(dec); + free_out_audio_packet_cb(packet); + } else { + packet->pts += ((double)ret) / bytes_per_second; + } + dec->clock_pos = packet->pts; + + return ret; +} diff --git a/src/internal/kitbuffer.c b/src/internal/kitbuffer.c new file mode 100644 index 0000000..5c492ea --- /dev/null +++ b/src/internal/kitbuffer.c @@ -0,0 +1,83 @@ +#include "kitchensink/internal/kitbuffer.h" + +#include +#include + +Kit_Buffer* Kit_CreateBuffer(unsigned int size, Kit_BufferFreeCallback free_cb) { + Kit_Buffer *b = calloc(1, sizeof(Kit_Buffer)); + if(b == NULL) { + return NULL; + } + b->size = size; + b->free_cb = free_cb; + b->data = calloc(size, sizeof(void*)); + if(b->data == NULL) { + free(b); + return NULL; + } + return b; +} + +void Kit_DestroyBuffer(Kit_Buffer *buffer) { + if(buffer == NULL) return; + Kit_ClearBuffer(buffer); + free(buffer->data); + free(buffer); +} + +void Kit_ClearBuffer(Kit_Buffer *buffer) { + void *data; + while((data = Kit_ReadBuffer(buffer)) != NULL) { + buffer->free_cb(data); + } +} + +void* Kit_ReadBuffer(Kit_Buffer *buffer) { + assert(buffer != NULL); + if(buffer->read_p < buffer->write_p) { + void *out = buffer->data[buffer->read_p % buffer->size]; + buffer->data[buffer->read_p % buffer->size] = NULL; + buffer->read_p++; + if(buffer->read_p >= buffer->size) { + buffer->read_p = buffer->read_p % buffer->size; + buffer->write_p = buffer->write_p % buffer->size; + } + return out; + } + return NULL; +} + +KIT_LOCAL void* Kit_PeekBuffer(const Kit_Buffer *buffer) { + assert(buffer != NULL); + return buffer->data[buffer->read_p % buffer->size]; +} + +KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) { + assert(buffer != NULL); + if(buffer->read_p < buffer->write_p) { + buffer->data[buffer->read_p % buffer->size] = NULL; + buffer->read_p++; + if(buffer->read_p >= buffer->size) { + buffer->read_p = buffer->read_p % buffer->size; + buffer->write_p = buffer->write_p % buffer->size; + } + } +} + +int Kit_WriteBuffer(Kit_Buffer *buffer, void *ptr) { + assert(buffer != NULL); + assert(ptr != NULL); + + if(!Kit_IsBufferFull(buffer)) { + buffer->data[buffer->write_p % buffer->size] = ptr; + buffer->write_p++; + return 0; + } + return 1; +} + +int Kit_IsBufferFull(const Kit_Buffer *buffer) { + int len = buffer->write_p - buffer->read_p; + int k = (len >= buffer->size); + return k; +} diff --git a/src/internal/kitdecoder.c b/src/internal/kitdecoder.c new file mode 100644 index 0000000..df78559 --- /dev/null +++ b/src/internal/kitdecoder.c @@ -0,0 +1,219 @@ +#include +#include + +#include + +#include "kitchensink/internal/kitdecoder.h" +#include "kitchensink/kiterror.h" + +#define BUFFER_IN_SIZE 32 + +static void free_in_video_packet_cb(void *packet) { + av_packet_free((AVPacket**)&packet); +} + +Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index, + int out_b_size, dec_free_packet_cb free_out_cb) { + assert(src != NULL); + assert(out_b_size > 0); + + AVCodecContext *codec_ctx = NULL; + AVCodec *codec = NULL; + AVFormatContext *format_ctx = src->format_ctx; + int bsizes[2] = {BUFFER_IN_SIZE, out_b_size}; + dec_free_packet_cb free_hooks[2] = {free_in_video_packet_cb, free_out_cb}; + + // Make sure index seems correct + if(stream_index >= (int)format_ctx->nb_streams || stream_index < 0) { + Kit_SetError("Invalid stream %d", stream_index); + goto exit_0; + } + + // Allocate decoder and make sure allocation was a success + Kit_Decoder *dec = calloc(1, sizeof(Kit_Decoder)); + if(dec == NULL) { + Kit_SetError("Unable to allocate kit decoder for stream %d", stream_index); + goto exit_0; + } + + // Find audio decoder + codec = avcodec_find_decoder(format_ctx->streams[stream_index]->codec->codec_id); + if(!codec) { + Kit_SetError("No suitable decoder found for stream %d", stream_index); + goto exit_1; + } + + // Allocate a context for the codec + codec_ctx = avcodec_alloc_context3(codec); + if(avcodec_copy_context(codec_ctx, format_ctx->streams[stream_index]->codec) != 0) { + Kit_SetError("Unable to copy audio codec context for stream %d", stream_index); + goto exit_1; + } + + // Open the stream + if(avcodec_open2(codec_ctx, codec, NULL) < 0) { + Kit_SetError("Unable to open codec for stream %d", stream_index); + goto exit_2; + } + + // Set index and codec + dec->stream_index = stream_index; + dec->codec_ctx = codec_ctx; + dec->format_ctx = format_ctx; + + // Allocate input/output ringbuffers and locks + for(int i = 0; i < 2; i++) { + dec->buffer[i] = Kit_CreateBuffer(bsizes[i], free_hooks[i]); + if(dec->buffer[i] == NULL) { + Kit_SetError("Unable to allocate ringbuffer type %d for stream %d", i, stream_index); + goto exit_3; + } + + dec->lock[i] = SDL_CreateMutex(); + if(dec->lock[i] == NULL) { + Kit_SetError("Unable to allocate mutex type %d for stream %d", i, stream_index); + goto exit_3; + } + } + + // That's that + return dec; + +exit_3: + for(int i = 0; i < 2; i++) { + SDL_DestroyMutex(dec->lock[i]); + Kit_DestroyBuffer(dec->buffer[i]); + } + avcodec_close(codec_ctx); +exit_2: + avcodec_free_context(&codec_ctx); +exit_1: + free(dec); +exit_0: + return NULL; +} + +void Kit_SetDecoderClockSync(Kit_Decoder *dec, double sync) { + if(dec == NULL) return; + dec->clock_sync = sync; +} + +void Kit_ChangeDecoderClockSync(Kit_Decoder *dec, double sync) { + if(dec == NULL) return; + dec->clock_sync += sync; +} + +int Kit_WriteDecoderInput(Kit_Decoder *dec, AVPacket *packet) { + assert(dec != NULL); + int ret = 1; + if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { + ret = Kit_WriteBuffer(dec->buffer[KIT_DEC_IN], packet); + SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); + } + return ret; +} + +bool Kit_CanWriteDecoderInput(Kit_Decoder *dec) { + assert(dec != NULL); + bool ret = false; + if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { + ret = !Kit_IsBufferFull(dec->buffer[KIT_DEC_IN]); + SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); + } + return ret; +} + +AVPacket* Kit_ReadDecoderInput(Kit_Decoder *dec) { + assert(dec != NULL); + AVPacket *ret = NULL; + if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { + ret = Kit_ReadBuffer(dec->buffer[KIT_DEC_IN]); + SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); + } + return ret; +} + +int Kit_WriteDecoderOutput(Kit_Decoder *dec, void *packet) { + assert(dec != NULL); + int ret = 1; + if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { + ret = Kit_WriteBuffer(dec->buffer[KIT_DEC_OUT], packet); + SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); + } + return ret; +} + +void* Kit_PeekDecoderOutput(Kit_Decoder *dec) { + assert(dec != NULL); + void *ret = NULL; + if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { + ret = Kit_PeekBuffer(dec->buffer[KIT_DEC_OUT]); + SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); + } + return ret; +} + +void Kit_AdvanceDecoderOutput(Kit_Decoder *dec) { + assert(dec != NULL); + if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { + Kit_AdvanceBuffer(dec->buffer[KIT_DEC_OUT]); + SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); + } +} + +void Kit_ClearDecoderBuffers(Kit_Decoder *dec) { + if(dec == NULL) return; + if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { + Kit_ClearBuffer(dec->buffer[KIT_DEC_IN]); + SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); + } + if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { + Kit_ClearBuffer(dec->buffer[KIT_DEC_OUT]); + SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); + } + avcodec_flush_buffers(dec->codec_ctx); +} + +int Kit_RunDecoder(Kit_Decoder *dec) { + if(dec == NULL) return 0; + + AVPacket *in_packet; + int is_output_full; + int ret; + + // First, check if there is room in output buffer + if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { + is_output_full = Kit_IsBufferFull(dec->buffer[KIT_DEC_OUT]); + SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); + } + if(is_output_full) { + return 0; + } + + // Then, see if we have incoming data + in_packet = Kit_ReadDecoderInput(dec); + if(in_packet == NULL) { + return 0; + } + + // Run decoder with incoming packet + ret = dec->dec_decode(dec, in_packet); + + // Free raw packet before returning + av_packet_free(&in_packet); + return ret; +} + +void Kit_CloseDecoder(Kit_Decoder *dec) { + if(dec == NULL) return; + if(dec->dec_close) { + dec->dec_close(dec); + } + for(int i = 0; i < 2; i++) { + SDL_DestroyMutex(dec->lock[i]); + Kit_DestroyBuffer(dec->buffer[i]); + } + avcodec_close(dec->codec_ctx); + avcodec_free_context(&dec->codec_ctx); + free(dec); +} diff --git a/src/internal/kithelpers.c b/src/internal/kithelpers.c new file mode 100644 index 0000000..25759be --- /dev/null +++ b/src/internal/kithelpers.c @@ -0,0 +1,30 @@ +#include +#include + +#include "kitchensink/internal/kithelpers.h" + +static const char * const font_mime[] = { + "application/x-font-ttf", + "application/x-font-truetype", + "application/x-truetype-font", + "application/x-font-opentype", + "application/vnd.ms-opentype", + "application/font-sfnt", + NULL +}; + +double _GetSystemTime() { + return (double)av_gettime() / 1000000.0; +} + +bool attachment_is_font(AVStream *stream) { + AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE); + if(tag) { + for(int n = 0; font_mime[n]; n++) { + if(av_strcasecmp(font_mime[n], tag->value) == 0) { + return true; + } + } + } + return false; +} diff --git a/src/internal/kitlibstate.c b/src/internal/kitlibstate.c new file mode 100644 index 0000000..0dd4e6b --- /dev/null +++ b/src/internal/kitlibstate.c @@ -0,0 +1,7 @@ +#include "kitchensink/internal/kitlibstate.h" + +static Kit_LibraryState _librarystate = {0, NULL}; + +Kit_LibraryState* Kit_GetLibraryState() { + return &_librarystate; +} diff --git a/src/internal/kitlist.c b/src/internal/kitlist.c new file mode 100644 index 0000000..b6d861c --- /dev/null +++ b/src/internal/kitlist.c @@ -0,0 +1,79 @@ +#include "kitchensink/internal/kitlist.h" + +#include +#include +#include + +Kit_List* Kit_CreateList(unsigned int size, Kit_ListFreeCallback free_cb) { + Kit_List *m = calloc(1, sizeof(Kit_List)); + if(m == NULL) { + return NULL; + } + m->size = size; + m->free_cb = free_cb; + m->data = calloc(size, sizeof(void*)); + if(m->data == NULL) { + free(m); + return NULL; + } + return m; +} + +void Kit_DestroyList(Kit_List *list) { + if(list == NULL) return; + Kit_ClearList(list); + free(list->data); + free(list); +} + +void Kit_ClearList(Kit_List *list) { + assert(list != NULL); + for(unsigned int i = 0; i < list->size; i++) { + if(list->data[i] != NULL) { + list->free_cb(list->data[i]); + list->data[i] = NULL; + } + } + list->length = 0; +} + +void Kit_RemoveFromList(Kit_List *list, unsigned int iterator) { + assert(list != NULL); + list->free_cb(list->data[iterator-1]); + list->data[iterator-1] = NULL; + list->length--; +} + +void* Kit_IterateList(const Kit_List *list, unsigned int *iterator) { + assert(list != NULL); + assert(iterator != NULL); + while((*iterator) < list->size) { + void *ptr = list->data[(*iterator)]; + *iterator += 1; + if(ptr != NULL) { + return ptr; + } + } + return NULL; +} + +int Kit_WriteList(Kit_List *list, void *ptr) { + assert(list != NULL); + assert(ptr != NULL); + if(list->length >= list->size) { + return 1; + } + for(unsigned int i = 0; i < list->size; i++) { + if(list->data[i] == NULL) { + list->data[i] = ptr; + list->length++; + return 0; + } + } + return 1; +} + +int Kit_GetListLength(const Kit_List *list) { + assert(list != NULL); + return list->length; +} diff --git a/src/internal/kitringbuffer.c b/src/internal/kitringbuffer.c new file mode 100644 index 0000000..1711d52 --- /dev/null +++ b/src/internal/kitringbuffer.c @@ -0,0 +1,172 @@ +/* + * Ringbuffer + * + * Copyright (c) 2016, Tuomas Virtanen + * license: MIT; see LICENSE for details. +*/ + +#include "kitchensink/internal/kitringbuffer.h" + +#include +#include +#include +#include + +/** + * Creates a new ringbuffer with the given size. + * @param size Size for the new ringbuffer + * @return Ringbuffer handle + */ +Kit_RingBuffer* Kit_CreateRingBuffer(unsigned int size) { + Kit_RingBuffer *rb = calloc(1, sizeof(Kit_RingBuffer)); + if(rb == NULL) return NULL; + rb->size = size; + rb->data = malloc(size); + if(rb->data == NULL) { + free(rb); + return NULL; + } + return rb; +} + +/** + * Deletes the given ringbuffer. + * @param rb Ringbuffer to be deleted. + */ +void Kit_DestroyRingBuffer(Kit_RingBuffer* rb) { + if(rb == NULL) return; + free(rb->data); + free(rb); +} + +/** + * Writes to the given ringbuffer. If given length is larger than the amount + * the ringbuffer can fit, only the data that fits will be written. + * @param rb Ringbuffer to write to. + * @param data Data to write + * @param len Data length + * @return Amount of data that was actually written. + */ +int Kit_WriteRingBuffer(Kit_RingBuffer *rb, const char* data, int len) { + int k; + len = (len > (rb->size - rb->len)) ? (rb->size - rb->len) : len; + if(rb->len < rb->size) { + if(len + rb->wpos > rb->size) { + k = (len + rb->wpos) % rb->size; + memcpy((rb->data + rb->wpos), data, len - k); + memcpy(rb->data, data+(len-k), k); + } else { + memcpy((rb->data + rb->wpos), data, len); + } + rb->len += len; + rb->wpos += len; + if(rb->wpos >= rb->size) { + rb->wpos = rb->wpos % rb->size; + } + return len; + } + return 0; +} + +/** + * Reads data from ringbuffer. If ringbuffer has less data than was requested, + * only the available data will be read. + * @param rb Ringbuffer to read from. + * @param data Buffer to read into. + * @param len How much data do we want + * @return Amount of data that was actually read. + */ +int Kit_ReadRingBuffer(Kit_RingBuffer *rb, char* data, int len) { + int k; + len = (len > rb->len) ? rb->len : len; + if(rb->len > 0) { + if(len + rb->rpos > rb->size) { + k = (len + rb->rpos) % rb->size; + memcpy(data, (rb->data + rb->rpos), len-k); + memcpy(data+(len-k), (rb->data), k); + } else { + memcpy(data, (rb->data + rb->rpos), len); + } + rb->len -= len; + rb->rpos += len; + if(rb->rpos >= rb->size) { + rb->rpos = rb->rpos % rb->size; + } + return len; + } + return 0; +} + +/** + * Peeks into the given ringbuffer. Technically same as rb_read, but does not advance + * the internal position pointer. In other words, you may peek as many times as you wish, + * and will always get the same data. + * @param rb Ringbuffer to peek into. + * @param data Buffer to read into + * @param len How much data do we need + * @return Amount of data actually read + */ +int Kit_PeekRingBuffer(const Kit_RingBuffer *rb, char* data, int len) { + int k; + len = (len > rb->len) ? rb->len : len; + if(rb->len > 0) { + if(len + rb->rpos > rb->size) { + k = (len + rb->rpos) % rb->size; + memcpy(data, (rb->data + rb->rpos), len-k); + memcpy(data+(len-k), (rb->data), k); + } else { + memcpy(data, (rb->data + rb->rpos), len); + } + return len; + } + return 0; +} + +/** + * Advances the internal position counter by given amount. Note that counter can only be + * advanced by the amount of unreadable data in ringbuffer. + * @param rb Ringbuffer to handle + * @param len How much should the position counter be increased + * @return How much the position counter was actually increased. + */ +int Kit_AdvanceRingBuffer(Kit_RingBuffer *rb, int len) { + len = (len > rb->len) ? rb->len : len; + if(rb->len > 0) { + rb->len -= len; + rb->rpos += len; + if(rb->rpos >= rb->size) { + rb->rpos = rb->rpos % rb->size; + } + return len; + } + return 0; +} + +/** + * Returns the current length of the Ringbuffer. In other words, how much data + * the ringbuffer contains + * @param rb Ringbuffer to handle + * @return Data in ringbuffer (in bytes). + */ +int Kit_GetRingBufferLength(const Kit_RingBuffer *rb) { + return rb->len; +} + +/** + * Returns the size of the ringbuffer. In other words, the maximum amount of data + * the ringbuffer can hold. + * @param rb Ringbuffer to handle + * @return Size of the ringbuffer + */ +int Kit_GetRingBufferSize(const Kit_RingBuffer *rb) { + return rb->size; +} + +/** + * Returns the free size of the ringbuffer. + * @param rb Ringbuffer to handle + * @return Free size in the ringbuffer + */ +int Kit_GetRingBufferFree(const Kit_RingBuffer *rb) { + return rb->size - rb->len; +} diff --git a/src/internal/kitsubtitle.c b/src/internal/kitsubtitle.c new file mode 100644 index 0000000..09e2afa --- /dev/null +++ b/src/internal/kitsubtitle.c @@ -0,0 +1,445 @@ +#include + +#include +#include +#include + +#include "kitchensink/kiterror.h" +#include "kitchensink/internal/kitlibstate.h" +#include "kitchensink/internal/kitsubtitle.h" +#include "kitchensink/internal/kitlist.h" +#include "kitchensink/internal/kithelpers.h" + +// For compatibility +#ifndef ASS_FONTPROVIDER_AUTODETECT +#define ASS_FONTPROVIDER_AUTODETECT 1 +#endif + +#define KIT_SUBTITLE_OUT_SIZE 512 + +typedef struct Kit_SubtitleDecoder { + Kit_SubtitleFormat *format; + ASS_Renderer *ass_renderer; + ASS_Track *ass_track; +} Kit_SubtitleDecoder; + +typedef struct Kit_SubtitlePacket { + double pts_start; + double pts_end; + SDL_Rect *rect; + SDL_Surface *surface; + SDL_Texture *texture; +} Kit_SubtitlePacket; + +/* +static Kit_SubtitlePacket* _CreateSubtitlePacket(double pts_start, double pts_end, SDL_Rect *rect, SDL_Surface *surface) { + Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket)); + p->pts_start = pts_start; + p->pts_end = pts_end; + p->surface = surface; + p->rect = rect; + p->texture = NULL; // Cached texture + return p; +} + + +void _HandleBitmapSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub, AVSubtitleRect *rect) { + if(rect->nb_colors == 256) { + // Paletted image based subtitles. Convert and set palette. + SDL_Surface *s = SDL_CreateRGBSurfaceFrom( + rect->data[0], + rect->w, rect->h, 8, + rect->linesize[0], + 0, 0, 0, 0); + + SDL_SetPaletteColors(s->format->palette, (SDL_Color*)rect->data[1], 0, 256); + + Uint32 rmask, gmask, bmask, amask; + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0xff000000; + gmask = 0x00ff0000; + bmask = 0x0000ff00; + amask = 0x000000ff; + #else + rmask = 0x000000ff; + gmask = 0x0000ff00; + bmask = 0x00ff0000; + amask = 0xff000000; + #endif + SDL_Surface *tmp = SDL_CreateRGBSurface( + 0, rect->w, rect->h, 32, + rmask, gmask, bmask, amask); + SDL_BlitSurface(s, NULL, tmp, NULL); + SDL_FreeSurface(s); + + SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect)); + dst_rect->x = rect->x; + dst_rect->y = rect->y; + dst_rect->w = rect->w; + dst_rect->h = rect->h; + + double start = pts + (sub->start_display_time / 1000.0f); + double end = -1; + if(sub->end_display_time < UINT_MAX) { + end = pts + (sub->end_display_time / 1000.0f); + } + + spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp); + } +} + +static void _ProcessAssSubtitleRect(Kit_Player *player, AVSubtitleRect *rect) { + ass_process_data((ASS_Track*)player->ass_track, rect->ass, strlen(rect->ass)); +} + +static void _ProcessAssImage(SDL_Surface *surface, const ASS_Image *img) { + int x, y; + // libass headers claim img->color is RGBA, but the alpha is 0. + unsigned char r = ((img->color) >> 24) & 0xFF; + unsigned char g = ((img->color) >> 16) & 0xFF; + unsigned char b = ((img->color) >> 8) & 0xFF; + unsigned char *src = img->bitmap; + unsigned char *dst = (unsigned char*)surface->pixels; + + for(y = 0; y < img->h; y++) { + for(x = 0; x < img->w; x++) { + dst[x * 4 + 0] = r; + dst[x * 4 + 1] = g; + dst[x * 4 + 2] = b; + dst[x * 4 + 3] = src[x]; + } + src += img->stride; + dst += surface->pitch; + } +} + +static void _HandleAssSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub) { + double start = pts + (sub->start_display_time / 1000.0f); + double end = pts + (sub->end_display_time / 1000.0f); + + // Process current chunk of data + unsigned int now = start * 1000; + int change = 0; + ASS_Image *images = ass_render_frame((ASS_Renderer*)player->ass_renderer, (ASS_Track*)player->ass_track, now, &change); + + // Convert to SDL_Surfaces + if(change > 0) { + ASS_Image *now = images; + if(now != NULL) { + do { + Uint32 rmask, gmask, bmask, amask; + #if SDL_BYTEORDER == SDL_BIG_ENDIAN + rmask = 0xff000000; + gmask = 0x00ff0000; + bmask = 0x0000ff00; + amask = 0x000000ff; + #else + rmask = 0x000000ff; + gmask = 0x0000ff00; + bmask = 0x00ff0000; + amask = 0xff000000; + #endif + SDL_Surface *tmp = SDL_CreateRGBSurface( + 0, now->w, now->h, 32, + rmask, gmask, bmask, amask); + + _ProcessAssImage(tmp, now); + + SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect)); + dst_rect->x = now->dst_x; + dst_rect->y = now->dst_y; + dst_rect->w = now->w; + dst_rect->h = now->h; + + spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp); + } while((now = now->next) != NULL); + } + } +} + +void _HandleSubtitlePacket(Kit_Player *player, AVPacket *packet) { + assert(player != NULL); + assert(packet != NULL); + + int frame_finished; + int len; + AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx; + AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx; + Kit_SubtitlePacket *tmp = NULL; + unsigned int it; + AVSubtitle sub; + memset(&sub, 0, sizeof(AVSubtitle)); + + if(packet->size > 0) { + len = avcodec_decode_subtitle2(scodec_ctx, &sub, &frame_finished, packet); + if(len < 0) { + return; + } + + if(frame_finished) { + // Get pts + double pts = 0; + if(packet->pts != AV_NOPTS_VALUE) { + pts = packet->pts; + pts *= av_q2d(fmt_ctx->streams[player->src->sstream_idx]->time_base); + } + + // Convert subtitles to SDL_Surface and create a packet + Kit_SubtitlePacket *spackets[KIT_SBUFFERSIZE]; + memset(spackets, 0, sizeof(Kit_SubtitlePacket*) * KIT_SBUFFERSIZE); + + int n = 0; + bool has_ass = false; + for(int r = 0; r < sub.num_rects; r++) { + switch(sub.rects[r]->type) { + case SUBTITLE_BITMAP: + _HandleBitmapSubtitle(spackets, &n, player, pts, &sub, sub.rects[r]); + break; + case SUBTITLE_ASS: + _ProcessAssSubtitleRect(player, sub.rects[r]); + has_ass = true; + break; + case SUBTITLE_TEXT: + break; + case SUBTITLE_NONE: + break; + } + } + + // Process libass content + if(has_ass) { + _HandleAssSubtitle(spackets, &n, player, pts, &sub); + } + + // Lock, write to subtitle buffer, unlock + if(SDL_LockMutex(player->smutex) == 0) { + if(has_ass) { + Kit_ClearList((Kit_List*)player->sbuffer); + } else { + // Clear out old subtitles that should only be valid until next (this) subtitle + it = 0; + while((tmp = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { + if(tmp->pts_end < 0) { + Kit_RemoveFromList((Kit_List*)player->sbuffer, it); + } + } + } + + // Add new subtitle + for(int i = 0; i < KIT_SBUFFERSIZE; i++) { + Kit_SubtitlePacket *spacket = spackets[i]; + if(spacket != NULL) { + if(Kit_WriteList((Kit_List*)player->sbuffer, spacket) == 0) { + spackets[i] = NULL; + } + } + } + + // Unlock subtitle buffer + SDL_UnlockMutex(player->smutex); + } + + // Couldn't write packet, free memory + for(int i = 0; i < KIT_SBUFFERSIZE; i++) { + if(spackets[i] != NULL) { + _FreeSubtitlePacket(spackets[i]); + } + } + } + } +} +*/ + +static void free_out_subtitle_packet_cb(void *packet) { + Kit_SubtitlePacket *p = packet; + SDL_FreeSurface(p->surface); + if(p->texture) { + SDL_DestroyTexture(p->texture); + } + free(p->rect); + free(p); +} + +static int dec_decode_subtitle_cb(Kit_Decoder *dec, AVPacket *in_packet) { + assert(dec != NULL); + assert(in_packet != NULL); + + int frame_finished; + int len; + + AVSubtitle sub; + memset(&sub, 0, sizeof(AVSubtitle)); + + if(in_packet->size > 0) { + len = avcodec_decode_subtitle2(dec->codec_ctx, &sub, &frame_finished, in_packet); + if(len < 0) { + return 1; + } + + if(frame_finished) { + // Get pts + double pts = 0; + if(in_packet->dts != AV_NOPTS_VALUE) { + pts = in_packet->pts; + pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); + } + + + // TODO: Implement more stuff + } + } + + return 1; +} + +static void dec_close_subtitle_cb(Kit_Decoder *dec) { + if(dec == NULL) return; + + Kit_SubtitleDecoder *subtitle_dec = dec->userdata; + /* + if(subtitle_dec->ass_track != NULL) { + ass_free_track(subtitle_dec->ass_track); + } + if(subtitle_dec->ass_renderer != NULL) { + ass_renderer_done(subtitle_dec->ass_renderer); + }*/ + free(subtitle_dec); +} + +Kit_Decoder* Kit_CreateSubtitleDecoder(const Kit_Source *src, Kit_SubtitleFormat *format, int w, int h) { + assert(src != NULL); + assert(format != NULL); + if(src->subtitle_stream_index < 0) { + return NULL; + } + + // First the generic decoder component ... + Kit_Decoder *dec = Kit_CreateDecoder( + src, src->subtitle_stream_index, + KIT_SUBTITLE_OUT_SIZE, + free_out_subtitle_packet_cb); + if(dec == NULL) { + goto exit_0; + } + + // Find formats + format->is_enabled = true; + format->stream_index = src->subtitle_stream_index; + + // ... then allocate the subtitle decoder + Kit_SubtitleDecoder *subtitle_dec = calloc(1, sizeof(Kit_SubtitleDecoder)); + if(subtitle_dec == NULL) { + goto exit_1; + } + + /* + // Initialize libass renderer + Kit_LibraryState *state = Kit_GetLibraryState(); + subtitle_dec->ass_renderer = ass_renderer_init(state->libass_handle); + if(subtitle_dec->ass_renderer == NULL) { + Kit_SetError("Unable to initialize libass renderer"); + goto exit_2; + } + + // Read fonts from attachment streams and give them to libass + AVFormatContext *format_ctx = src->format_ctx; + for (int j = 0; j < format_ctx->nb_streams; j++) { + AVStream *st = format_ctx->streams[j]; + if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) { + const AVDictionaryEntry *tag = av_dict_get( + st->metadata, + "filename", + NULL, + AV_DICT_MATCH_CASE); + if(tag) { + ass_add_font( + state->libass_handle, + tag->value, + (char*)st->codec->extradata, + st->codec->extradata_size); + } + } + } + + // Init libass fonts and window frame size + ass_set_fonts( + subtitle_dec->ass_renderer, + NULL, "sans-serif", + ASS_FONTPROVIDER_AUTODETECT, + NULL, 1); + ass_set_frame_size(subtitle_dec->ass_renderer, w, h); + ass_set_hinting(subtitle_dec->ass_renderer, ASS_HINTING_NONE); + + // Initialize libass track + subtitle_dec->ass_track = ass_new_track(state->libass_handle); + if(subtitle_dec->ass_track == NULL) { + Kit_SetError("Unable to initialize libass track"); + goto exit_3; + } + + // Set up libass track headers (ffmpeg provides these) + if(dec->codec_ctx->subtitle_header) { + ass_process_codec_private( + subtitle_dec->ass_track, + (char*)dec->codec_ctx->subtitle_header, + dec->codec_ctx->subtitle_header_size); + }*/ + + // Set callbacks and userdata, and we're go + subtitle_dec->format = format; + dec->dec_decode = dec_decode_subtitle_cb; + dec->dec_close = dec_close_subtitle_cb; + dec->userdata = subtitle_dec; + return dec; + +//exit_3: + //ass_renderer_done(subtitle_dec->ass_renderer); +//exit_2: + //free(subtitle_dec); +exit_1: + Kit_CloseDecoder(dec); +exit_0: + return NULL; +} + +int Kit_GetSubtitleDecoderData(Kit_Decoder *dec, SDL_Renderer *renderer) { + assert(dec != NULL); + assert(renderer != NULL); + + +/* + unsigned int it; + Kit_SubtitlePacket *packet = NULL; + + // Current sync timestamp + double cur_subtitle_ts = _GetSystemTime() - player->clock_sync; + + // Read a packet from buffer, if one exists. Stop here if not. + if(SDL_LockMutex(player->smutex) == 0) { + // Check if refresh is required and remove old subtitles + it = 0; + while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { + if(packet->pts_end >= 0 && packet->pts_end < cur_subtitle_ts) { + Kit_RemoveFromList((Kit_List*)player->sbuffer, it); + } + } + + // Render subtitle bitmaps + it = 0; + while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { + if(packet->texture == NULL) { + packet->texture = SDL_CreateTextureFromSurface(renderer, packet->surface); + SDL_SetTextureBlendMode(packet->texture, SDL_BLENDMODE_BLEND); + } + SDL_RenderCopy(renderer, packet->texture, NULL, packet->rect); + } + + // Unlock subtitle buffer mutex. + SDL_UnlockMutex(player->smutex); + } else { + Kit_SetError("Unable to lock subtitle buffer mutex"); + return 0; + } +*/ + return 0; +} diff --git a/src/internal/kitvideo.c b/src/internal/kitvideo.c new file mode 100644 index 0000000..079d650 --- /dev/null +++ b/src/internal/kitvideo.c @@ -0,0 +1,275 @@ +#include + +#include +#include +#include + +#include "kitchensink/kiterror.h" +#include "kitchensink/internal/kitdecoder.h" +#include "kitchensink/internal/kithelpers.h" +#include "kitchensink/internal/kitbuffer.h" +#include "kitchensink/internal/kitvideo.h" +#include "kitchensink/internal/kitlog.h" + +#define KIT_VIDEO_OUT_SIZE 1 +#define VIDEO_SYNC_THRESHOLD 0.01 + +typedef struct Kit_VideoDecoder { + Kit_VideoFormat *format; + struct SwsContext *sws; + AVFrame *scratch_frame; +} Kit_VideoDecoder; + +typedef struct Kit_VideoPacket { + double pts; + AVFrame *frame; +} Kit_VideoPacket; + + +static Kit_VideoPacket* _CreateVideoPacket(AVFrame *frame, double pts) { + Kit_VideoPacket *p = calloc(1, sizeof(Kit_VideoPacket)); + p->frame = frame; + p->pts = pts; + return p; +} + +static void _FindPixelFormat(enum AVPixelFormat fmt, unsigned int *out_fmt) { + switch(fmt) { + case AV_PIX_FMT_YUV420P9: + case AV_PIX_FMT_YUV420P10: + case AV_PIX_FMT_YUV420P12: + case AV_PIX_FMT_YUV420P14: + case AV_PIX_FMT_YUV420P16: + case AV_PIX_FMT_YUV420P: + *out_fmt = SDL_PIXELFORMAT_YV12; + break; + case AV_PIX_FMT_YUYV422: + *out_fmt = SDL_PIXELFORMAT_YUY2; + break; + case AV_PIX_FMT_UYVY422: + *out_fmt = SDL_PIXELFORMAT_UYVY; + break; + default: + *out_fmt = SDL_PIXELFORMAT_ABGR8888; + break; + } +} + +static enum AVPixelFormat _FindAVPixelFormat(unsigned int fmt) { + switch(fmt) { + case SDL_PIXELFORMAT_IYUV: return AV_PIX_FMT_YUV420P; + case SDL_PIXELFORMAT_YV12: return AV_PIX_FMT_YUV420P; + case SDL_PIXELFORMAT_YUY2: return AV_PIX_FMT_YUYV422; + case SDL_PIXELFORMAT_UYVY: return AV_PIX_FMT_UYVY422; + case SDL_PIXELFORMAT_ARGB8888: return AV_PIX_FMT_BGRA; + case SDL_PIXELFORMAT_ABGR8888: return AV_PIX_FMT_RGBA; + default: + return AV_PIX_FMT_NONE; + } +} + +static void free_out_video_packet_cb(void *packet) { + Kit_VideoPacket *p = packet; + av_freep(&p->frame->data[0]); + av_frame_free(&p->frame); + free(p); +} + +static int dec_decode_video_cb(Kit_Decoder *dec, AVPacket *in_packet) { + assert(dec != NULL); + assert(in_packet != NULL); + + Kit_VideoDecoder *video_dec = dec->userdata; + int frame_finished; + + while(in_packet->size > 0) { + int len = avcodec_decode_video2(dec->codec_ctx, video_dec->scratch_frame, &frame_finished, in_packet); + if(len < 0) { + return 1; + } + + if(frame_finished) { + // Target frame + AVFrame *out_frame = av_frame_alloc(); + av_image_alloc( + out_frame->data, + out_frame->linesize, + dec->codec_ctx->width, + dec->codec_ctx->height, + _FindAVPixelFormat(video_dec->format->format), + 1); + + // Scale from source format to target format, don't touch the size + sws_scale( + video_dec->sws, + (const unsigned char * const *)video_dec->scratch_frame->data, + video_dec->scratch_frame->linesize, + 0, + dec->codec_ctx->height, + out_frame->data, + out_frame->linesize); + + // Get pts + double pts = 0; + if(in_packet->pts != AV_NOPTS_VALUE) { + pts = av_frame_get_best_effort_timestamp(video_dec->scratch_frame); + pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); + } + + // Lock, write to audio buffer, unlock + Kit_VideoPacket *out_packet = _CreateVideoPacket(out_frame, pts); + Kit_WriteDecoderOutput(dec, out_packet); + } + in_packet->size -= len; + in_packet->data += len; + } + + + return 1; +} + +static void dec_close_video_cb(Kit_Decoder *dec) { + if(dec == NULL) return; + + Kit_VideoDecoder *video_dec = dec->userdata; + if(video_dec->scratch_frame != NULL) { + av_frame_free(&video_dec->scratch_frame); + } + if(video_dec->sws != NULL) { + sws_freeContext(video_dec->sws); + } + free(video_dec); +} + +Kit_Decoder* Kit_CreateVideoDecoder(const Kit_Source *src, Kit_VideoFormat *format) { + assert(src != NULL); + assert(format != NULL); + if(src->video_stream_index < 0) { + return NULL; + } + + // First the generic decoder component ... + Kit_Decoder *dec = Kit_CreateDecoder( + src, src->video_stream_index, + KIT_VIDEO_OUT_SIZE, + free_out_video_packet_cb); + if(dec == NULL) { + goto exit_0; + } + + // Find formats + format->is_enabled = true; + format->width = dec->codec_ctx->width; + format->height = dec->codec_ctx->height; + format->stream_index = src->video_stream_index; + _FindPixelFormat(dec->codec_ctx->pix_fmt, &format->format); + + // ... then allocate the video decoder + Kit_VideoDecoder *video_dec = calloc(1, sizeof(Kit_VideoDecoder)); + if(video_dec == NULL) { + goto exit_1; + } + + // Create temporary video frame + video_dec->scratch_frame = av_frame_alloc(); + if(video_dec->scratch_frame == NULL) { + Kit_SetError("Unable to initialize temporary video frame"); + goto exit_2; + } + + // Create scaler + video_dec->sws = sws_getContext( + dec->codec_ctx->width, // Source w + dec->codec_ctx->height, // Source h + dec->codec_ctx->pix_fmt, // Source fmt + dec->codec_ctx->width, // Target w + dec->codec_ctx->height, // Target h + _FindAVPixelFormat(format->format), // Target fmt + SWS_BICUBIC, + NULL, NULL, NULL); + + if(video_dec->sws == NULL) { + Kit_SetError("Unable to initialize video converter context"); + goto exit_3; + } + + // Set callbacks and userdata, and we're go + video_dec->format = format; + dec->dec_decode = dec_decode_video_cb; + dec->dec_close = dec_close_video_cb; + dec->userdata = video_dec; + return dec; + +exit_3: + av_frame_free(&video_dec->scratch_frame); +exit_2: + free(video_dec); +exit_1: + Kit_CloseDecoder(dec); +exit_0: + return NULL; +} + +int Kit_GetVideoDecoderData(Kit_Decoder *dec, SDL_Texture *texture) { + assert(dec != NULL); + + Kit_VideoPacket *packet = Kit_PeekDecoderOutput(dec); + if(packet == NULL) { + return 0; + } + + Kit_VideoDecoder *video_dec = dec->userdata; + double sync_ts = _GetSystemTime() - dec->clock_sync; + + // Check if we want the packet + if(packet->pts > sync_ts + VIDEO_SYNC_THRESHOLD) { + // Video is ahead, don't show yet. + return 0; + } else if(packet->pts < sync_ts - VIDEO_SYNC_THRESHOLD) { + // Video is lagging, skip until we find a good PTS to continue from. + while(packet != NULL) { + Kit_AdvanceDecoderOutput(dec); + free_out_video_packet_cb(packet); + packet = Kit_PeekDecoderOutput(dec); + if(packet == NULL) { + break; + } else { + dec->clock_pos = packet->pts; + } + if(packet->pts > sync_ts - VIDEO_SYNC_THRESHOLD) { + break; + } + } + } + + // If we have no viable packet, just skip + if(packet == NULL) { + return 0; + } + + // Update output texture with current video data. + // Take formats into account. + switch(video_dec->format->format) { + case SDL_PIXELFORMAT_YV12: + case SDL_PIXELFORMAT_IYUV: + SDL_UpdateYUVTexture( + texture, NULL, + packet->frame->data[0], packet->frame->linesize[0], + packet->frame->data[1], packet->frame->linesize[1], + packet->frame->data[2], packet->frame->linesize[2]); + break; + default: + SDL_UpdateTexture( + texture, NULL, + packet->frame->data[0], + packet->frame->linesize[0]); + break; + } + + // Advance buffer, and free the decoded frame. + Kit_AdvanceDecoderOutput(dec); + free_out_video_packet_cb(packet); + dec->clock_pos = packet->pts; + + return 0; +} diff --git a/src/kitaudio.c b/src/kitaudio.c deleted file mode 100644 index e05948b..0000000 --- a/src/kitaudio.c +++ /dev/null @@ -1,306 +0,0 @@ -#include -#define __STDC_FORMAT_MACROS -#include - -#include -#include -#include -#include - -#include "kitchensink/kiterror.h" -#include "kitchensink/internal/kithelpers.h" -#include "kitchensink/internal/kitbuffer.h" -#include "kitchensink/internal/kitaudio.h" -#include "kitchensink/internal/kitringbuffer.h" -#include "kitchensink/internal/kitlog.h" - -#define KIT_AUDIO_IN_SIZE 32 -#define KIT_AUDIO_OUT_SIZE 16 -#define AUDIO_SYNC_THRESHOLD 0.05 - -typedef struct Kit_AudioDecoder { - Kit_AudioFormat *format; - SwrContext *swr; - AVFrame *scratch_frame; -} Kit_AudioDecoder; - -typedef struct Kit_AudioPacket { - double pts; - size_t original_size; - Kit_RingBuffer *rb; -} Kit_AudioPacket; - - -Kit_AudioPacket* _CreateAudioPacket(const char* data, size_t len, double pts) { - Kit_AudioPacket *p = calloc(1, sizeof(Kit_AudioPacket)); - p->rb = Kit_CreateRingBuffer(len); - Kit_WriteRingBuffer(p->rb, data, len); - p->pts = pts; - return p; -} - -void _FreeAudioPacket(void *ptr) { - Kit_AudioPacket *packet = ptr; - Kit_DestroyRingBuffer(packet->rb); - free(packet); -} - -enum AVSampleFormat _FindAVSampleFormat(int format) { - switch(format) { - case AUDIO_U8: return AV_SAMPLE_FMT_U8; - case AUDIO_S16SYS: return AV_SAMPLE_FMT_S16; - case AUDIO_S32SYS: return AV_SAMPLE_FMT_S32; - default: - return AV_SAMPLE_FMT_NONE; - } -} - -int64_t _FindAVChannelLayout(int channels) { - switch(channels) { - case 1: return AV_CH_LAYOUT_MONO; - case 2: return AV_CH_LAYOUT_STEREO; - case 4: return AV_CH_LAYOUT_QUAD; - case 6: return AV_CH_LAYOUT_5POINT1; - default: return AV_CH_LAYOUT_STEREO_DOWNMIX; - } -} - -void _FindAudioFormat(enum AVSampleFormat fmt, int *bytes, bool *is_signed, unsigned int *format) { - switch(fmt) { - case AV_SAMPLE_FMT_U8: - *bytes = 1; - *is_signed = false; - *format = AUDIO_U8; - break; - case AV_SAMPLE_FMT_S16: - *bytes = 2; - *is_signed = true; - *format = AUDIO_S16SYS; - break; - case AV_SAMPLE_FMT_S32: - *bytes = 4; - *is_signed = true; - *format = AUDIO_S32SYS; - break; - default: - *bytes = 2; - *is_signed = true; - *format = AUDIO_S16SYS; - break; - } -} - -static void free_out_audio_packet_cb(void *packet) { - Kit_AudioPacket *p = packet; - Kit_DestroyRingBuffer(p->rb); - free(p); -} - -static int dec_decode_audio_cb(Kit_Decoder *dec, AVPacket *in_packet) { - assert(dec != NULL); - assert(in_packet != NULL); - - Kit_AudioDecoder *audio_dec = dec->userdata; - int frame_finished; - int len, len2; - int dst_linesize; - int dst_nb_samples, dst_bufsize; - unsigned char **dst_data; - - // Decode as long as there is data - while(in_packet->size > 0) { - len = avcodec_decode_audio4(dec->codec_ctx, audio_dec->scratch_frame, &frame_finished, in_packet); - if(len < 0) { - return 1; - } - - if(frame_finished) { - dst_nb_samples = av_rescale_rnd( - audio_dec->scratch_frame->nb_samples, - audio_dec->format->samplerate, // Target samplerate - dec->codec_ctx->sample_rate, // Source samplerate - AV_ROUND_UP); - - av_samples_alloc_array_and_samples( - &dst_data, - &dst_linesize, - audio_dec->format->channels, - dst_nb_samples, - _FindAVSampleFormat(audio_dec->format->format), - 0); - - len2 = swr_convert( - audio_dec->swr, - dst_data, - audio_dec->scratch_frame->nb_samples, - (const unsigned char **)audio_dec->scratch_frame->extended_data, - audio_dec->scratch_frame->nb_samples); - - dst_bufsize = av_samples_get_buffer_size( - &dst_linesize, - audio_dec->format->channels, - len2, - _FindAVSampleFormat(audio_dec->format->format), 1); - - // Get pts for the packet - double pts = 0; - if(in_packet->pts != AV_NOPTS_VALUE) { - pts = av_frame_get_best_effort_timestamp(audio_dec->scratch_frame); - pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); - } - - // Lock, write to audio buffer, unlock - Kit_AudioPacket *out_packet = _CreateAudioPacket( - (char*)dst_data[0], (size_t)dst_bufsize, pts); - Kit_WriteDecoderOutput(dec, out_packet); - - // Free temps - av_freep(&dst_data[0]); - av_freep(&dst_data); - } - - in_packet->size -= len; - in_packet->data += len; - } - - - return 1; -} - -static void dec_close_audio_cb(Kit_Decoder *dec) { - if(dec == NULL) return; - - Kit_AudioDecoder *audio_dec = dec->userdata; - if(audio_dec->scratch_frame != NULL) { - av_frame_free(&audio_dec->scratch_frame); - } - if(audio_dec->swr != NULL) { - swr_free(&audio_dec->swr); - } - free(audio_dec); -} - -Kit_Decoder* Kit_CreateAudioDecoder(const Kit_Source *src, Kit_AudioFormat *format) { - assert(src != NULL); - assert(format != NULL); - if(src->audio_stream_index < 0) { - return NULL; - } - - // First the generic decoder component ... - Kit_Decoder *dec = Kit_CreateDecoder( - src, src->audio_stream_index, - KIT_AUDIO_IN_SIZE, KIT_AUDIO_OUT_SIZE, - free_out_audio_packet_cb); - if(dec == NULL) { - goto exit_0; - } - - // Find formats - format->samplerate = dec->codec_ctx->sample_rate; - format->channels = dec->codec_ctx->channels > 2 ? 2 : dec->codec_ctx->channels; - format->is_enabled = true; - format->stream_index = src->audio_stream_index; - _FindAudioFormat(dec->codec_ctx->sample_fmt, &format->bytes, &format->is_signed, &format->format); - - // ... then allocate the audio decoder - Kit_AudioDecoder *audio_dec = calloc(1, sizeof(Kit_AudioDecoder)); - if(audio_dec == NULL) { - goto exit_1; - } - - // Create temporary audio frame - audio_dec->scratch_frame = av_frame_alloc(); - if(audio_dec->scratch_frame == NULL) { - Kit_SetError("Unable to initialize temporary audio frame"); - goto exit_2; - } - - // Create resampler - audio_dec->swr = swr_alloc_set_opts( - NULL, - _FindAVChannelLayout(format->channels), // Target channel layout - _FindAVSampleFormat(format->format), // Target fmt - format->samplerate, // Target samplerate - dec->codec_ctx->channel_layout, // Source channel layout - dec->codec_ctx->sample_fmt, // Source fmt - dec->codec_ctx->sample_rate, // Source samplerate - 0, NULL); - - if(swr_init(audio_dec->swr) != 0) { - Kit_SetError("Unable to initialize audio resampler context"); - goto exit_3; - } - - // Set callbacks and userdata, and we're go - audio_dec->format = format; - dec->dec_decode = dec_decode_audio_cb; - dec->dec_close = dec_close_audio_cb; - dec->userdata = audio_dec; - return dec; - -exit_3: - av_frame_free(&audio_dec->scratch_frame); -exit_2: - free(audio_dec); -exit_1: - Kit_CloseDecoder(dec); -exit_0: - return NULL; -} - -int Kit_GetAudioDecoderData(Kit_Decoder *dec, unsigned char *buf, int len) { - assert(dec != NULL); - - Kit_AudioPacket *packet = Kit_PeekDecoderOutput(dec); - if(packet == NULL) { - return 0; - } - - int ret = 0; - Kit_AudioDecoder *audio_dec = dec->userdata; - int bytes_per_sample = audio_dec->format->bytes * audio_dec->format->channels; - double bytes_per_second = bytes_per_sample * audio_dec->format->samplerate; - double sync_ts = _GetSystemTime() - dec->clock_sync; - - if(packet->pts > sync_ts + AUDIO_SYNC_THRESHOLD) { - return 0; - } else if(packet->pts < sync_ts - AUDIO_SYNC_THRESHOLD) { - // Audio is lagging, skip until good pts is found - while(1) { - Kit_AdvanceDecoderOutput(dec); - free_out_audio_packet_cb(packet); - packet = Kit_PeekDecoderOutput(dec); - if(packet == NULL) { - break; - } else { - dec->clock_pos = packet->pts; - } - if(packet->pts > sync_ts - AUDIO_SYNC_THRESHOLD) { - break; - } - } - } - - // If we have no viable packet, just skip - if(packet == NULL) { - return 0; - } - - // Read data from packet ringbuffer - if(len > 0) { - ret = Kit_ReadRingBuffer(packet->rb, (char*)buf, len); - } - - // If ringbuffer is cleared, kill packet and advance buffer. - // Otherwise forward the pts value for the current packet. - if(Kit_GetRingBufferLength(packet->rb) == 0) { - Kit_AdvanceDecoderOutput(dec); - free_out_audio_packet_cb(packet); - } else { - packet->pts += ((double)ret) / bytes_per_second; - } - dec->clock_pos = packet->pts; - - return ret; -} diff --git a/src/kitbuffer.c b/src/kitbuffer.c deleted file mode 100644 index 5c492ea..0000000 --- a/src/kitbuffer.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "kitchensink/internal/kitbuffer.h" - -#include -#include - -Kit_Buffer* Kit_CreateBuffer(unsigned int size, Kit_BufferFreeCallback free_cb) { - Kit_Buffer *b = calloc(1, sizeof(Kit_Buffer)); - if(b == NULL) { - return NULL; - } - b->size = size; - b->free_cb = free_cb; - b->data = calloc(size, sizeof(void*)); - if(b->data == NULL) { - free(b); - return NULL; - } - return b; -} - -void Kit_DestroyBuffer(Kit_Buffer *buffer) { - if(buffer == NULL) return; - Kit_ClearBuffer(buffer); - free(buffer->data); - free(buffer); -} - -void Kit_ClearBuffer(Kit_Buffer *buffer) { - void *data; - while((data = Kit_ReadBuffer(buffer)) != NULL) { - buffer->free_cb(data); - } -} - -void* Kit_ReadBuffer(Kit_Buffer *buffer) { - assert(buffer != NULL); - if(buffer->read_p < buffer->write_p) { - void *out = buffer->data[buffer->read_p % buffer->size]; - buffer->data[buffer->read_p % buffer->size] = NULL; - buffer->read_p++; - if(buffer->read_p >= buffer->size) { - buffer->read_p = buffer->read_p % buffer->size; - buffer->write_p = buffer->write_p % buffer->size; - } - return out; - } - return NULL; -} - -KIT_LOCAL void* Kit_PeekBuffer(const Kit_Buffer *buffer) { - assert(buffer != NULL); - return buffer->data[buffer->read_p % buffer->size]; -} - -KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) { - assert(buffer != NULL); - if(buffer->read_p < buffer->write_p) { - buffer->data[buffer->read_p % buffer->size] = NULL; - buffer->read_p++; - if(buffer->read_p >= buffer->size) { - buffer->read_p = buffer->read_p % buffer->size; - buffer->write_p = buffer->write_p % buffer->size; - } - } -} - -int Kit_WriteBuffer(Kit_Buffer *buffer, void *ptr) { - assert(buffer != NULL); - assert(ptr != NULL); - - if(!Kit_IsBufferFull(buffer)) { - buffer->data[buffer->write_p % buffer->size] = ptr; - buffer->write_p++; - return 0; - } - return 1; -} - -int Kit_IsBufferFull(const Kit_Buffer *buffer) { - int len = buffer->write_p - buffer->read_p; - int k = (len >= buffer->size); - return k; -} diff --git a/src/kitdecoder.c b/src/kitdecoder.c deleted file mode 100644 index 2018097..0000000 --- a/src/kitdecoder.c +++ /dev/null @@ -1,220 +0,0 @@ -#include -#include - -#include - -#include "kitchensink/internal/kitdecoder.h" -#include "kitchensink/kiterror.h" - - -static void free_in_video_packet_cb(void *packet) { - av_packet_free((AVPacket**)&packet); -} - -Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index, - int in_b_size, int out_b_size, - dec_free_packet_cb free_out_cb) { - assert(src != NULL); - assert(in_b_size > 0); - assert(out_b_size > 0); - - AVCodecContext *codec_ctx = NULL; - AVCodec *codec = NULL; - AVFormatContext *format_ctx = src->format_ctx; - int bsizes[2] = {in_b_size, out_b_size}; - dec_free_packet_cb free_hooks[2] = {free_in_video_packet_cb, free_out_cb}; - - // Make sure index seems correct - if(stream_index >= (int)format_ctx->nb_streams || stream_index < 0) { - Kit_SetError("Invalid stream %d", stream_index); - goto exit_0; - } - - // Allocate decoder and make sure allocation was a success - Kit_Decoder *dec = calloc(1, sizeof(Kit_Decoder)); - if(dec == NULL) { - Kit_SetError("Unable to allocate kit decoder for stream %d", stream_index); - goto exit_0; - } - - // Find audio decoder - codec = avcodec_find_decoder(format_ctx->streams[stream_index]->codec->codec_id); - if(!codec) { - Kit_SetError("No suitable decoder found for stream %d", stream_index); - goto exit_1; - } - - // Allocate a context for the codec - codec_ctx = avcodec_alloc_context3(codec); - if(avcodec_copy_context(codec_ctx, format_ctx->streams[stream_index]->codec) != 0) { - Kit_SetError("Unable to copy audio codec context for stream %d", stream_index); - goto exit_1; - } - - // Open the stream - if(avcodec_open2(codec_ctx, codec, NULL) < 0) { - Kit_SetError("Unable to open codec for stream %d", stream_index); - goto exit_2; - } - - // Set index and codec - dec->stream_index = stream_index; - dec->codec_ctx = codec_ctx; - dec->format_ctx = format_ctx; - - // Allocate input/output ringbuffers and locks - for(int i = 0; i < 2; i++) { - dec->buffer[i] = Kit_CreateBuffer(bsizes[i], free_hooks[i]); - if(dec->buffer[i] == NULL) { - Kit_SetError("Unable to allocate ringbuffer type %d for stream %d", i, stream_index); - goto exit_3; - } - - dec->lock[i] = SDL_CreateMutex(); - if(dec->lock[i] == NULL) { - Kit_SetError("Unable to allocate mutex type %d for stream %d", i, stream_index); - goto exit_3; - } - } - - // That's that - return dec; - -exit_3: - for(int i = 0; i < 2; i++) { - SDL_DestroyMutex(dec->lock[i]); - Kit_DestroyBuffer(dec->buffer[i]); - } - avcodec_close(codec_ctx); -exit_2: - avcodec_free_context(&codec_ctx); -exit_1: - free(dec); -exit_0: - return NULL; -} - -void Kit_SetDecoderClockSync(Kit_Decoder *dec, double sync) { - if(dec == NULL) return; - dec->clock_sync = sync; -} - -void Kit_ChangeDecoderClockSync(Kit_Decoder *dec, double sync) { - if(dec == NULL) return; - dec->clock_sync += sync; -} - -int Kit_WriteDecoderInput(Kit_Decoder *dec, AVPacket *packet) { - assert(dec != NULL); - int ret = 1; - if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { - ret = Kit_WriteBuffer(dec->buffer[KIT_DEC_IN], packet); - SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); - } - return ret; -} - -bool Kit_CanWriteDecoderInput(Kit_Decoder *dec) { - assert(dec != NULL); - bool ret = false; - if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { - ret = !Kit_IsBufferFull(dec->buffer[KIT_DEC_IN]); - SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); - } - return ret; -} - -AVPacket* Kit_ReadDecoderInput(Kit_Decoder *dec) { - assert(dec != NULL); - AVPacket *ret = NULL; - if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { - ret = Kit_ReadBuffer(dec->buffer[KIT_DEC_IN]); - SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); - } - return ret; -} - -int Kit_WriteDecoderOutput(Kit_Decoder *dec, void *packet) { - assert(dec != NULL); - int ret = 1; - if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { - ret = Kit_WriteBuffer(dec->buffer[KIT_DEC_OUT], packet); - SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); - } - return ret; -} - -void* Kit_PeekDecoderOutput(Kit_Decoder *dec) { - assert(dec != NULL); - void *ret = NULL; - if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { - ret = Kit_PeekBuffer(dec->buffer[KIT_DEC_OUT]); - SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); - } - return ret; -} - -void Kit_AdvanceDecoderOutput(Kit_Decoder *dec) { - assert(dec != NULL); - if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { - Kit_AdvanceBuffer(dec->buffer[KIT_DEC_OUT]); - SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); - } -} - -void Kit_ClearDecoderBuffers(Kit_Decoder *dec) { - if(dec == NULL) return; - if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) { - Kit_ClearBuffer(dec->buffer[KIT_DEC_IN]); - SDL_UnlockMutex(dec->lock[KIT_DEC_IN]); - } - if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { - Kit_ClearBuffer(dec->buffer[KIT_DEC_OUT]); - SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); - } - avcodec_flush_buffers(dec->codec_ctx); -} - -int Kit_RunDecoder(Kit_Decoder *dec) { - if(dec == NULL) return 0; - - AVPacket *in_packet; - int is_output_full; - int ret; - - // First, check if there is room in output buffer - if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) { - is_output_full = Kit_IsBufferFull(dec->buffer[KIT_DEC_OUT]); - SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]); - } - if(is_output_full) { - return 0; - } - - // Then, see if we have incoming data - in_packet = Kit_ReadDecoderInput(dec); - if(in_packet == NULL) { - return 0; - } - - // Run decoder with incoming packet - ret = dec->dec_decode(dec, in_packet); - - // Free raw packet before returning - av_packet_free(&in_packet); - return ret; -} - -void Kit_CloseDecoder(Kit_Decoder *dec) { - if(dec == NULL) return; - if(dec->dec_close) { - dec->dec_close(dec); - } - for(int i = 0; i < 2; i++) { - SDL_DestroyMutex(dec->lock[i]); - Kit_DestroyBuffer(dec->buffer[i]); - } - avcodec_close(dec->codec_ctx); - avcodec_free_context(&dec->codec_ctx); - free(dec); -} diff --git a/src/kithelpers.c b/src/kithelpers.c deleted file mode 100644 index 9c44d1e..0000000 --- a/src/kithelpers.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include - -#include "kitchensink/internal/kithelpers.h" - -static const char * const font_mime[] = { - "application/x-font-ttf", - "application/x-font-truetype", - "application/x-truetype-font", - "application/x-font-opentype", - "application/vnd.ms-opentype", - "application/font-sfnt", - NULL -}; - -double _GetSystemTime() { - return (double)av_gettime() / 1000000.0; -} - -bool attachment_is_font(AVStream *stream) { - AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE); - if(tag) { - for(int n = 0; font_mime[n]; n++) { - if(av_strcasecmp(font_mime[n], tag->value) == 0) { - return true; - } - } - } - return false; -} \ No newline at end of file diff --git a/src/kitlibstate.c b/src/kitlibstate.c deleted file mode 100644 index 0dd4e6b..0000000 --- a/src/kitlibstate.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "kitchensink/internal/kitlibstate.h" - -static Kit_LibraryState _librarystate = {0, NULL}; - -Kit_LibraryState* Kit_GetLibraryState() { - return &_librarystate; -} diff --git a/src/kitlist.c b/src/kitlist.c deleted file mode 100644 index b6d861c..0000000 --- a/src/kitlist.c +++ /dev/null @@ -1,79 +0,0 @@ -#include "kitchensink/internal/kitlist.h" - -#include -#include -#include - -Kit_List* Kit_CreateList(unsigned int size, Kit_ListFreeCallback free_cb) { - Kit_List *m = calloc(1, sizeof(Kit_List)); - if(m == NULL) { - return NULL; - } - m->size = size; - m->free_cb = free_cb; - m->data = calloc(size, sizeof(void*)); - if(m->data == NULL) { - free(m); - return NULL; - } - return m; -} - -void Kit_DestroyList(Kit_List *list) { - if(list == NULL) return; - Kit_ClearList(list); - free(list->data); - free(list); -} - -void Kit_ClearList(Kit_List *list) { - assert(list != NULL); - for(unsigned int i = 0; i < list->size; i++) { - if(list->data[i] != NULL) { - list->free_cb(list->data[i]); - list->data[i] = NULL; - } - } - list->length = 0; -} - -void Kit_RemoveFromList(Kit_List *list, unsigned int iterator) { - assert(list != NULL); - list->free_cb(list->data[iterator-1]); - list->data[iterator-1] = NULL; - list->length--; -} - -void* Kit_IterateList(const Kit_List *list, unsigned int *iterator) { - assert(list != NULL); - assert(iterator != NULL); - while((*iterator) < list->size) { - void *ptr = list->data[(*iterator)]; - *iterator += 1; - if(ptr != NULL) { - return ptr; - } - } - return NULL; -} - -int Kit_WriteList(Kit_List *list, void *ptr) { - assert(list != NULL); - assert(ptr != NULL); - if(list->length >= list->size) { - return 1; - } - for(unsigned int i = 0; i < list->size; i++) { - if(list->data[i] == NULL) { - list->data[i] = ptr; - list->length++; - return 0; - } - } - return 1; -} - -int Kit_GetListLength(const Kit_List *list) { - assert(list != NULL); - return list->length; -} diff --git a/src/kitringbuffer.c b/src/kitringbuffer.c deleted file mode 100644 index 1711d52..0000000 --- a/src/kitringbuffer.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Ringbuffer - * - * Copyright (c) 2016, Tuomas Virtanen - * license: MIT; see LICENSE for details. -*/ - -#include "kitchensink/internal/kitringbuffer.h" - -#include -#include -#include -#include - -/** - * Creates a new ringbuffer with the given size. - * @param size Size for the new ringbuffer - * @return Ringbuffer handle - */ -Kit_RingBuffer* Kit_CreateRingBuffer(unsigned int size) { - Kit_RingBuffer *rb = calloc(1, sizeof(Kit_RingBuffer)); - if(rb == NULL) return NULL; - rb->size = size; - rb->data = malloc(size); - if(rb->data == NULL) { - free(rb); - return NULL; - } - return rb; -} - -/** - * Deletes the given ringbuffer. - * @param rb Ringbuffer to be deleted. - */ -void Kit_DestroyRingBuffer(Kit_RingBuffer* rb) { - if(rb == NULL) return; - free(rb->data); - free(rb); -} - -/** - * Writes to the given ringbuffer. If given length is larger than the amount - * the ringbuffer can fit, only the data that fits will be written. - * @param rb Ringbuffer to write to. - * @param data Data to write - * @param len Data length - * @return Amount of data that was actually written. - */ -int Kit_WriteRingBuffer(Kit_RingBuffer *rb, const char* data, int len) { - int k; - len = (len > (rb->size - rb->len)) ? (rb->size - rb->len) : len; - if(rb->len < rb->size) { - if(len + rb->wpos > rb->size) { - k = (len + rb->wpos) % rb->size; - memcpy((rb->data + rb->wpos), data, len - k); - memcpy(rb->data, data+(len-k), k); - } else { - memcpy((rb->data + rb->wpos), data, len); - } - rb->len += len; - rb->wpos += len; - if(rb->wpos >= rb->size) { - rb->wpos = rb->wpos % rb->size; - } - return len; - } - return 0; -} - -/** - * Reads data from ringbuffer. If ringbuffer has less data than was requested, - * only the available data will be read. - * @param rb Ringbuffer to read from. - * @param data Buffer to read into. - * @param len How much data do we want - * @return Amount of data that was actually read. - */ -int Kit_ReadRingBuffer(Kit_RingBuffer *rb, char* data, int len) { - int k; - len = (len > rb->len) ? rb->len : len; - if(rb->len > 0) { - if(len + rb->rpos > rb->size) { - k = (len + rb->rpos) % rb->size; - memcpy(data, (rb->data + rb->rpos), len-k); - memcpy(data+(len-k), (rb->data), k); - } else { - memcpy(data, (rb->data + rb->rpos), len); - } - rb->len -= len; - rb->rpos += len; - if(rb->rpos >= rb->size) { - rb->rpos = rb->rpos % rb->size; - } - return len; - } - return 0; -} - -/** - * Peeks into the given ringbuffer. Technically same as rb_read, but does not advance - * the internal position pointer. In other words, you may peek as many times as you wish, - * and will always get the same data. - * @param rb Ringbuffer to peek into. - * @param data Buffer to read into - * @param len How much data do we need - * @return Amount of data actually read - */ -int Kit_PeekRingBuffer(const Kit_RingBuffer *rb, char* data, int len) { - int k; - len = (len > rb->len) ? rb->len : len; - if(rb->len > 0) { - if(len + rb->rpos > rb->size) { - k = (len + rb->rpos) % rb->size; - memcpy(data, (rb->data + rb->rpos), len-k); - memcpy(data+(len-k), (rb->data), k); - } else { - memcpy(data, (rb->data + rb->rpos), len); - } - return len; - } - return 0; -} - -/** - * Advances the internal position counter by given amount. Note that counter can only be - * advanced by the amount of unreadable data in ringbuffer. - * @param rb Ringbuffer to handle - * @param len How much should the position counter be increased - * @return How much the position counter was actually increased. - */ -int Kit_AdvanceRingBuffer(Kit_RingBuffer *rb, int len) { - len = (len > rb->len) ? rb->len : len; - if(rb->len > 0) { - rb->len -= len; - rb->rpos += len; - if(rb->rpos >= rb->size) { - rb->rpos = rb->rpos % rb->size; - } - return len; - } - return 0; -} - -/** - * Returns the current length of the Ringbuffer. In other words, how much data - * the ringbuffer contains - * @param rb Ringbuffer to handle - * @return Data in ringbuffer (in bytes). - */ -int Kit_GetRingBufferLength(const Kit_RingBuffer *rb) { - return rb->len; -} - -/** - * Returns the size of the ringbuffer. In other words, the maximum amount of data - * the ringbuffer can hold. - * @param rb Ringbuffer to handle - * @return Size of the ringbuffer - */ -int Kit_GetRingBufferSize(const Kit_RingBuffer *rb) { - return rb->size; -} - -/** - * Returns the free size of the ringbuffer. - * @param rb Ringbuffer to handle - * @return Free size in the ringbuffer - */ -int Kit_GetRingBufferFree(const Kit_RingBuffer *rb) { - return rb->size - rb->len; -} diff --git a/src/kitsubtitle.c b/src/kitsubtitle.c deleted file mode 100644 index 60fd144..0000000 --- a/src/kitsubtitle.c +++ /dev/null @@ -1,446 +0,0 @@ -#include - -#include -#include -#include - -#include "kitchensink/kiterror.h" -#include "kitchensink/internal/kitlibstate.h" -#include "kitchensink/internal/kitsubtitle.h" -#include "kitchensink/internal/kitlist.h" -#include "kitchensink/internal/kithelpers.h" - -// For compatibility -#ifndef ASS_FONTPROVIDER_AUTODETECT -#define ASS_FONTPROVIDER_AUTODETECT 1 -#endif - -#define KIT_SUBTITLE_IN_SIZE 64 -#define KIT_SUBTITLE_OUT_SIZE 64 - -typedef struct Kit_SubtitleDecoder { - Kit_SubtitleFormat *format; - ASS_Renderer *ass_renderer; - ASS_Track *ass_track; -} Kit_SubtitleDecoder; - -typedef struct Kit_SubtitlePacket { - double pts_start; - double pts_end; - SDL_Rect *rect; - SDL_Surface *surface; - SDL_Texture *texture; -} Kit_SubtitlePacket; - -/* -static Kit_SubtitlePacket* _CreateSubtitlePacket(double pts_start, double pts_end, SDL_Rect *rect, SDL_Surface *surface) { - Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket)); - p->pts_start = pts_start; - p->pts_end = pts_end; - p->surface = surface; - p->rect = rect; - p->texture = NULL; // Cached texture - return p; -} - - -void _HandleBitmapSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub, AVSubtitleRect *rect) { - if(rect->nb_colors == 256) { - // Paletted image based subtitles. Convert and set palette. - SDL_Surface *s = SDL_CreateRGBSurfaceFrom( - rect->data[0], - rect->w, rect->h, 8, - rect->linesize[0], - 0, 0, 0, 0); - - SDL_SetPaletteColors(s->format->palette, (SDL_Color*)rect->data[1], 0, 256); - - Uint32 rmask, gmask, bmask, amask; - #if SDL_BYTEORDER == SDL_BIG_ENDIAN - rmask = 0xff000000; - gmask = 0x00ff0000; - bmask = 0x0000ff00; - amask = 0x000000ff; - #else - rmask = 0x000000ff; - gmask = 0x0000ff00; - bmask = 0x00ff0000; - amask = 0xff000000; - #endif - SDL_Surface *tmp = SDL_CreateRGBSurface( - 0, rect->w, rect->h, 32, - rmask, gmask, bmask, amask); - SDL_BlitSurface(s, NULL, tmp, NULL); - SDL_FreeSurface(s); - - SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect)); - dst_rect->x = rect->x; - dst_rect->y = rect->y; - dst_rect->w = rect->w; - dst_rect->h = rect->h; - - double start = pts + (sub->start_display_time / 1000.0f); - double end = -1; - if(sub->end_display_time < UINT_MAX) { - end = pts + (sub->end_display_time / 1000.0f); - } - - spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp); - } -} - -static void _ProcessAssSubtitleRect(Kit_Player *player, AVSubtitleRect *rect) { - ass_process_data((ASS_Track*)player->ass_track, rect->ass, strlen(rect->ass)); -} - -static void _ProcessAssImage(SDL_Surface *surface, const ASS_Image *img) { - int x, y; - // libass headers claim img->color is RGBA, but the alpha is 0. - unsigned char r = ((img->color) >> 24) & 0xFF; - unsigned char g = ((img->color) >> 16) & 0xFF; - unsigned char b = ((img->color) >> 8) & 0xFF; - unsigned char *src = img->bitmap; - unsigned char *dst = (unsigned char*)surface->pixels; - - for(y = 0; y < img->h; y++) { - for(x = 0; x < img->w; x++) { - dst[x * 4 + 0] = r; - dst[x * 4 + 1] = g; - dst[x * 4 + 2] = b; - dst[x * 4 + 3] = src[x]; - } - src += img->stride; - dst += surface->pitch; - } -} - -static void _HandleAssSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub) { - double start = pts + (sub->start_display_time / 1000.0f); - double end = pts + (sub->end_display_time / 1000.0f); - - // Process current chunk of data - unsigned int now = start * 1000; - int change = 0; - ASS_Image *images = ass_render_frame((ASS_Renderer*)player->ass_renderer, (ASS_Track*)player->ass_track, now, &change); - - // Convert to SDL_Surfaces - if(change > 0) { - ASS_Image *now = images; - if(now != NULL) { - do { - Uint32 rmask, gmask, bmask, amask; - #if SDL_BYTEORDER == SDL_BIG_ENDIAN - rmask = 0xff000000; - gmask = 0x00ff0000; - bmask = 0x0000ff00; - amask = 0x000000ff; - #else - rmask = 0x000000ff; - gmask = 0x0000ff00; - bmask = 0x00ff0000; - amask = 0xff000000; - #endif - SDL_Surface *tmp = SDL_CreateRGBSurface( - 0, now->w, now->h, 32, - rmask, gmask, bmask, amask); - - _ProcessAssImage(tmp, now); - - SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect)); - dst_rect->x = now->dst_x; - dst_rect->y = now->dst_y; - dst_rect->w = now->w; - dst_rect->h = now->h; - - spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp); - } while((now = now->next) != NULL); - } - } -} - -void _HandleSubtitlePacket(Kit_Player *player, AVPacket *packet) { - assert(player != NULL); - assert(packet != NULL); - - int frame_finished; - int len; - AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx; - AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx; - Kit_SubtitlePacket *tmp = NULL; - unsigned int it; - AVSubtitle sub; - memset(&sub, 0, sizeof(AVSubtitle)); - - if(packet->size > 0) { - len = avcodec_decode_subtitle2(scodec_ctx, &sub, &frame_finished, packet); - if(len < 0) { - return; - } - - if(frame_finished) { - // Get pts - double pts = 0; - if(packet->pts != AV_NOPTS_VALUE) { - pts = packet->pts; - pts *= av_q2d(fmt_ctx->streams[player->src->sstream_idx]->time_base); - } - - // Convert subtitles to SDL_Surface and create a packet - Kit_SubtitlePacket *spackets[KIT_SBUFFERSIZE]; - memset(spackets, 0, sizeof(Kit_SubtitlePacket*) * KIT_SBUFFERSIZE); - - int n = 0; - bool has_ass = false; - for(int r = 0; r < sub.num_rects; r++) { - switch(sub.rects[r]->type) { - case SUBTITLE_BITMAP: - _HandleBitmapSubtitle(spackets, &n, player, pts, &sub, sub.rects[r]); - break; - case SUBTITLE_ASS: - _ProcessAssSubtitleRect(player, sub.rects[r]); - has_ass = true; - break; - case SUBTITLE_TEXT: - break; - case SUBTITLE_NONE: - break; - } - } - - // Process libass content - if(has_ass) { - _HandleAssSubtitle(spackets, &n, player, pts, &sub); - } - - // Lock, write to subtitle buffer, unlock - if(SDL_LockMutex(player->smutex) == 0) { - if(has_ass) { - Kit_ClearList((Kit_List*)player->sbuffer); - } else { - // Clear out old subtitles that should only be valid until next (this) subtitle - it = 0; - while((tmp = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { - if(tmp->pts_end < 0) { - Kit_RemoveFromList((Kit_List*)player->sbuffer, it); - } - } - } - - // Add new subtitle - for(int i = 0; i < KIT_SBUFFERSIZE; i++) { - Kit_SubtitlePacket *spacket = spackets[i]; - if(spacket != NULL) { - if(Kit_WriteList((Kit_List*)player->sbuffer, spacket) == 0) { - spackets[i] = NULL; - } - } - } - - // Unlock subtitle buffer - SDL_UnlockMutex(player->smutex); - } - - // Couldn't write packet, free memory - for(int i = 0; i < KIT_SBUFFERSIZE; i++) { - if(spackets[i] != NULL) { - _FreeSubtitlePacket(spackets[i]); - } - } - } - } -} -*/ - -static void free_out_subtitle_packet_cb(void *packet) { - Kit_SubtitlePacket *p = packet; - SDL_FreeSurface(p->surface); - if(p->texture) { - SDL_DestroyTexture(p->texture); - } - free(p->rect); - free(p); -} - -static int dec_decode_subtitle_cb(Kit_Decoder *dec, AVPacket *in_packet) { - assert(dec != NULL); - assert(in_packet != NULL); - - int frame_finished; - int len; - - AVSubtitle sub; - memset(&sub, 0, sizeof(AVSubtitle)); - - if(in_packet->size > 0) { - len = avcodec_decode_subtitle2(dec->codec_ctx, &sub, &frame_finished, in_packet); - if(len < 0) { - return 1; - } - - if(frame_finished) { - // Get pts - double pts = 0; - if(in_packet->dts != AV_NOPTS_VALUE) { - pts = in_packet->pts; - pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); - } - - - // TODO: Implement more stuff - } - } - - return 1; -} - -static void dec_close_subtitle_cb(Kit_Decoder *dec) { - if(dec == NULL) return; - - Kit_SubtitleDecoder *subtitle_dec = dec->userdata; - /* - if(subtitle_dec->ass_track != NULL) { - ass_free_track(subtitle_dec->ass_track); - } - if(subtitle_dec->ass_renderer != NULL) { - ass_renderer_done(subtitle_dec->ass_renderer); - }*/ - free(subtitle_dec); -} - -Kit_Decoder* Kit_CreateSubtitleDecoder(const Kit_Source *src, Kit_SubtitleFormat *format, int w, int h) { - assert(src != NULL); - assert(format != NULL); - if(src->subtitle_stream_index < 0) { - return NULL; - } - - // First the generic decoder component ... - Kit_Decoder *dec = Kit_CreateDecoder( - src, src->subtitle_stream_index, - KIT_SUBTITLE_IN_SIZE, KIT_SUBTITLE_OUT_SIZE, - free_out_subtitle_packet_cb); - if(dec == NULL) { - goto exit_0; - } - - // Find formats - format->is_enabled = true; - format->stream_index = src->subtitle_stream_index; - - // ... then allocate the subtitle decoder - Kit_SubtitleDecoder *subtitle_dec = calloc(1, sizeof(Kit_SubtitleDecoder)); - if(subtitle_dec == NULL) { - goto exit_1; - } - - /* - // Initialize libass renderer - Kit_LibraryState *state = Kit_GetLibraryState(); - subtitle_dec->ass_renderer = ass_renderer_init(state->libass_handle); - if(subtitle_dec->ass_renderer == NULL) { - Kit_SetError("Unable to initialize libass renderer"); - goto exit_2; - } - - // Read fonts from attachment streams and give them to libass - AVFormatContext *format_ctx = src->format_ctx; - for (int j = 0; j < format_ctx->nb_streams; j++) { - AVStream *st = format_ctx->streams[j]; - if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) { - const AVDictionaryEntry *tag = av_dict_get( - st->metadata, - "filename", - NULL, - AV_DICT_MATCH_CASE); - if(tag) { - ass_add_font( - state->libass_handle, - tag->value, - (char*)st->codec->extradata, - st->codec->extradata_size); - } - } - } - - // Init libass fonts and window frame size - ass_set_fonts( - subtitle_dec->ass_renderer, - NULL, "sans-serif", - ASS_FONTPROVIDER_AUTODETECT, - NULL, 1); - ass_set_frame_size(subtitle_dec->ass_renderer, w, h); - ass_set_hinting(subtitle_dec->ass_renderer, ASS_HINTING_NONE); - - // Initialize libass track - subtitle_dec->ass_track = ass_new_track(state->libass_handle); - if(subtitle_dec->ass_track == NULL) { - Kit_SetError("Unable to initialize libass track"); - goto exit_3; - } - - // Set up libass track headers (ffmpeg provides these) - if(dec->codec_ctx->subtitle_header) { - ass_process_codec_private( - subtitle_dec->ass_track, - (char*)dec->codec_ctx->subtitle_header, - dec->codec_ctx->subtitle_header_size); - }*/ - - // Set callbacks and userdata, and we're go - subtitle_dec->format = format; - dec->dec_decode = dec_decode_subtitle_cb; - dec->dec_close = dec_close_subtitle_cb; - dec->userdata = subtitle_dec; - return dec; - -//exit_3: - //ass_renderer_done(subtitle_dec->ass_renderer); -//exit_2: - //free(subtitle_dec); -exit_1: - Kit_CloseDecoder(dec); -exit_0: - return NULL; -} - -int Kit_GetSubtitleDecoderData(Kit_Decoder *dec, SDL_Renderer *renderer) { - assert(dec != NULL); - assert(renderer != NULL); - - -/* - unsigned int it; - Kit_SubtitlePacket *packet = NULL; - - // Current sync timestamp - double cur_subtitle_ts = _GetSystemTime() - player->clock_sync; - - // Read a packet from buffer, if one exists. Stop here if not. - if(SDL_LockMutex(player->smutex) == 0) { - // Check if refresh is required and remove old subtitles - it = 0; - while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { - if(packet->pts_end >= 0 && packet->pts_end < cur_subtitle_ts) { - Kit_RemoveFromList((Kit_List*)player->sbuffer, it); - } - } - - // Render subtitle bitmaps - it = 0; - while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) { - if(packet->texture == NULL) { - packet->texture = SDL_CreateTextureFromSurface(renderer, packet->surface); - SDL_SetTextureBlendMode(packet->texture, SDL_BLENDMODE_BLEND); - } - SDL_RenderCopy(renderer, packet->texture, NULL, packet->rect); - } - - // Unlock subtitle buffer mutex. - SDL_UnlockMutex(player->smutex); - } else { - Kit_SetError("Unable to lock subtitle buffer mutex"); - return 0; - } -*/ - return 0; -} diff --git a/src/kitvideo.c b/src/kitvideo.c deleted file mode 100644 index 4a83056..0000000 --- a/src/kitvideo.c +++ /dev/null @@ -1,276 +0,0 @@ -#include - -#include -#include -#include - -#include "kitchensink/kiterror.h" -#include "kitchensink/internal/kitdecoder.h" -#include "kitchensink/internal/kithelpers.h" -#include "kitchensink/internal/kitbuffer.h" -#include "kitchensink/internal/kitvideo.h" -#include "kitchensink/internal/kitlog.h" - -#define KIT_VIDEO_IN_SIZE 32 -#define KIT_VIDEO_OUT_SIZE 1 -#define VIDEO_SYNC_THRESHOLD 0.01 - -typedef struct Kit_VideoDecoder { - Kit_VideoFormat *format; - struct SwsContext *sws; - AVFrame *scratch_frame; -} Kit_VideoDecoder; - -typedef struct Kit_VideoPacket { - double pts; - AVFrame *frame; -} Kit_VideoPacket; - - -static Kit_VideoPacket* _CreateVideoPacket(AVFrame *frame, double pts) { - Kit_VideoPacket *p = calloc(1, sizeof(Kit_VideoPacket)); - p->frame = frame; - p->pts = pts; - return p; -} - -static void _FindPixelFormat(enum AVPixelFormat fmt, unsigned int *out_fmt) { - switch(fmt) { - case AV_PIX_FMT_YUV420P9: - case AV_PIX_FMT_YUV420P10: - case AV_PIX_FMT_YUV420P12: - case AV_PIX_FMT_YUV420P14: - case AV_PIX_FMT_YUV420P16: - case AV_PIX_FMT_YUV420P: - *out_fmt = SDL_PIXELFORMAT_YV12; - break; - case AV_PIX_FMT_YUYV422: - *out_fmt = SDL_PIXELFORMAT_YUY2; - break; - case AV_PIX_FMT_UYVY422: - *out_fmt = SDL_PIXELFORMAT_UYVY; - break; - default: - *out_fmt = SDL_PIXELFORMAT_ABGR8888; - break; - } -} - -static enum AVPixelFormat _FindAVPixelFormat(unsigned int fmt) { - switch(fmt) { - case SDL_PIXELFORMAT_IYUV: return AV_PIX_FMT_YUV420P; - case SDL_PIXELFORMAT_YV12: return AV_PIX_FMT_YUV420P; - case SDL_PIXELFORMAT_YUY2: return AV_PIX_FMT_YUYV422; - case SDL_PIXELFORMAT_UYVY: return AV_PIX_FMT_UYVY422; - case SDL_PIXELFORMAT_ARGB8888: return AV_PIX_FMT_BGRA; - case SDL_PIXELFORMAT_ABGR8888: return AV_PIX_FMT_RGBA; - default: - return AV_PIX_FMT_NONE; - } -} - -static void free_out_video_packet_cb(void *packet) { - Kit_VideoPacket *p = packet; - av_freep(&p->frame->data[0]); - av_frame_free(&p->frame); - free(p); -} - -static int dec_decode_video_cb(Kit_Decoder *dec, AVPacket *in_packet) { - assert(dec != NULL); - assert(in_packet != NULL); - - Kit_VideoDecoder *video_dec = dec->userdata; - int frame_finished; - - while(in_packet->size > 0) { - int len = avcodec_decode_video2(dec->codec_ctx, video_dec->scratch_frame, &frame_finished, in_packet); - if(len < 0) { - return 1; - } - - if(frame_finished) { - // Target frame - AVFrame *out_frame = av_frame_alloc(); - av_image_alloc( - out_frame->data, - out_frame->linesize, - dec->codec_ctx->width, - dec->codec_ctx->height, - _FindAVPixelFormat(video_dec->format->format), - 1); - - // Scale from source format to target format, don't touch the size - sws_scale( - video_dec->sws, - (const unsigned char * const *)video_dec->scratch_frame->data, - video_dec->scratch_frame->linesize, - 0, - dec->codec_ctx->height, - out_frame->data, - out_frame->linesize); - - // Get pts - double pts = 0; - if(in_packet->pts != AV_NOPTS_VALUE) { - pts = av_frame_get_best_effort_timestamp(video_dec->scratch_frame); - pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base); - } - - // Lock, write to audio buffer, unlock - Kit_VideoPacket *out_packet = _CreateVideoPacket(out_frame, pts); - Kit_WriteDecoderOutput(dec, out_packet); - } - in_packet->size -= len; - in_packet->data += len; - } - - - return 1; -} - -static void dec_close_video_cb(Kit_Decoder *dec) { - if(dec == NULL) return; - - Kit_VideoDecoder *video_dec = dec->userdata; - if(video_dec->scratch_frame != NULL) { - av_frame_free(&video_dec->scratch_frame); - } - if(video_dec->sws != NULL) { - sws_freeContext(video_dec->sws); - } - free(video_dec); -} - -Kit_Decoder* Kit_CreateVideoDecoder(const Kit_Source *src, Kit_VideoFormat *format) { - assert(src != NULL); - assert(format != NULL); - if(src->video_stream_index < 0) { - return NULL; - } - - // First the generic decoder component ... - Kit_Decoder *dec = Kit_CreateDecoder( - src, src->video_stream_index, - KIT_VIDEO_IN_SIZE, KIT_VIDEO_OUT_SIZE, - free_out_video_packet_cb); - if(dec == NULL) { - goto exit_0; - } - - // Find formats - format->is_enabled = true; - format->width = dec->codec_ctx->width; - format->height = dec->codec_ctx->height; - format->stream_index = src->video_stream_index; - _FindPixelFormat(dec->codec_ctx->pix_fmt, &format->format); - - // ... then allocate the video decoder - Kit_VideoDecoder *video_dec = calloc(1, sizeof(Kit_VideoDecoder)); - if(video_dec == NULL) { - goto exit_1; - } - - // Create temporary video frame - video_dec->scratch_frame = av_frame_alloc(); - if(video_dec->scratch_frame == NULL) { - Kit_SetError("Unable to initialize temporary video frame"); - goto exit_2; - } - - // Create scaler - video_dec->sws = sws_getContext( - dec->codec_ctx->width, // Source w - dec->codec_ctx->height, // Source h - dec->codec_ctx->pix_fmt, // Source fmt - dec->codec_ctx->width, // Target w - dec->codec_ctx->height, // Target h - _FindAVPixelFormat(format->format), // Target fmt - SWS_BICUBIC, - NULL, NULL, NULL); - - if(video_dec->sws == NULL) { - Kit_SetError("Unable to initialize video converter context"); - goto exit_3; - } - - // Set callbacks and userdata, and we're go - video_dec->format = format; - dec->dec_decode = dec_decode_video_cb; - dec->dec_close = dec_close_video_cb; - dec->userdata = video_dec; - return dec; - -exit_3: - av_frame_free(&video_dec->scratch_frame); -exit_2: - free(video_dec); -exit_1: - Kit_CloseDecoder(dec); -exit_0: - return NULL; -} - -int Kit_GetVideoDecoderData(Kit_Decoder *dec, SDL_Texture *texture) { - assert(dec != NULL); - - Kit_VideoPacket *packet = Kit_PeekDecoderOutput(dec); - if(packet == NULL) { - return 0; - } - - Kit_VideoDecoder *video_dec = dec->userdata; - double sync_ts = _GetSystemTime() - dec->clock_sync; - - // Check if we want the packet - if(packet->pts > sync_ts + VIDEO_SYNC_THRESHOLD) { - // Video is ahead, don't show yet. - return 0; - } else if(packet->pts < sync_ts - VIDEO_SYNC_THRESHOLD) { - // Video is lagging, skip until we find a good PTS to continue from. - while(packet != NULL) { - Kit_AdvanceDecoderOutput(dec); - free_out_video_packet_cb(packet); - packet = Kit_PeekDecoderOutput(dec); - if(packet == NULL) { - break; - } else { - dec->clock_pos = packet->pts; - } - if(packet->pts > sync_ts - VIDEO_SYNC_THRESHOLD) { - break; - } - } - } - - // If we have no viable packet, just skip - if(packet == NULL) { - return 0; - } - - // Update output texture with current video data. - // Take formats into account. - switch(video_dec->format->format) { - case SDL_PIXELFORMAT_YV12: - case SDL_PIXELFORMAT_IYUV: - SDL_UpdateYUVTexture( - texture, NULL, - packet->frame->data[0], packet->frame->linesize[0], - packet->frame->data[1], packet->frame->linesize[1], - packet->frame->data[2], packet->frame->linesize[2]); - break; - default: - SDL_UpdateTexture( - texture, NULL, - packet->frame->data[0], - packet->frame->linesize[0]); - break; - } - - // Advance buffer, and free the decoded frame. - Kit_AdvanceDecoderOutput(dec); - free_out_video_packet_cb(packet); - dec->clock_pos = packet->pts; - - return 0; -} -- cgit v1.2.3