summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTuomas Virtanen <katajakasa@gmail.com>2018-03-26 18:24:54 +0300
committerGitHub <noreply@github.com>2018-03-26 18:24:54 +0300
commitcd9cdcb9fbc6bbea56e28eb9e4e13f7036004266 (patch)
tree39e5197565c33d5227ded4bed435a4d95998bf0b /src
parent48c7daa8e91d96805548ef4d2fa9f4b925c26108 (diff)
parent9777f2b386f7846fe5ebfe04d3086c157baea2db (diff)
Merge pull request #27 from katajakasa/ttv-split-player
PR for SDL_kitchensink v1
Diffstat (limited to 'src')
-rw-r--r--src/internal/audio/kitaudio.c316
-rw-r--r--src/internal/kitdecoder.c235
-rw-r--r--src/internal/kitlibstate.c (renamed from src/kitlibstate.c)3
-rw-r--r--src/internal/libass.c24
-rw-r--r--src/internal/subtitle/kitsubtitle.c254
-rw-r--r--src/internal/subtitle/kitsubtitlepacket.c19
-rw-r--r--src/internal/subtitle/renderers/kitsubass.c217
-rw-r--r--src/internal/subtitle/renderers/kitsubimage.c93
-rw-r--r--src/internal/subtitle/renderers/kitsubrenderer.c33
-rw-r--r--src/internal/utils/kitbuffer.c (renamed from src/kitbuffer.c)27
-rw-r--r--src/internal/utils/kithelpers.c30
-rw-r--r--src/internal/utils/kitringbuffer.c (renamed from src/kitringbuffer.c)6
-rw-r--r--src/internal/video/kitvideo.c269
-rw-r--r--src/kiterror.c4
-rw-r--r--src/kitlib.c61
-rw-r--r--src/kitlist.c79
-rw-r--r--src/kitplayer.c1476
-rw-r--r--src/kitsource.c28
-rw-r--r--src/kitutils.c4
19 files changed, 1760 insertions, 1418 deletions
diff --git a/src/internal/audio/kitaudio.c b/src/internal/audio/kitaudio.c
new file mode 100644
index 0000000..447a74d
--- /dev/null
+++ b/src/internal/audio/kitaudio.c
@@ -0,0 +1,316 @@
+#include <assert.h>
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include <libavformat/avformat.h>
+#include <libavutil/samplefmt.h>
+#include <libswresample/swresample.h>
+#include <SDL2/SDL.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitbuffer.h"
+#include "kitchensink/internal/audio/kitaudio.h"
+#include "kitchensink/internal/utils/kitringbuffer.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+#define KIT_AUDIO_OUT_SIZE 64
+#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 _FindChannelLayout(uint64_t channel_layout, int *channels) {
+ switch(channel_layout) {
+ case AV_CH_LAYOUT_MONO:
+ *channels = 1;
+ break;
+ case AV_CH_LAYOUT_STEREO:
+ *channels = 2;
+ break;
+ case AV_CH_LAYOUT_QUAD:
+ *channels = 4;
+ break;
+ case AV_CH_LAYOUT_5POINT1:
+ *channels = 6;
+ break;
+ default:
+ *channels = 2;
+ }
+}
+
+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 presentation timestamp
+ double 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->is_enabled = true;
+ format->stream_index = src->audio_stream_index;
+ _FindChannelLayout(dec->codec_ctx->channel_layout, &format->channels);
+ _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);
+ dec->clock_pos = packet->pts;
+ free_out_audio_packet_cb(packet);
+ } else {
+ packet->pts += ((double)ret) / bytes_per_second;
+ dec->clock_pos = packet->pts;
+ }
+
+ return ret;
+}
diff --git a/src/internal/kitdecoder.c b/src/internal/kitdecoder.c
new file mode 100644
index 0000000..555c54f
--- /dev/null
+++ b/src/internal/kitdecoder.c
@@ -0,0 +1,235 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/kiterror.h"
+
+#define BUFFER_IN_SIZE 256
+
+static void free_in_video_packet_cb(void *packet) {
+ av_packet_free((AVPacket**)&packet);
+}
+
+Kit_Decoder* Kit_CreateDecoder(const Kit_Source *src, int stream_index,
+ int out_b_size, dec_free_packet_cb free_out_cb) {
+ 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_ClearDecoderOutput(Kit_Decoder *dec) {
+ if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) {
+ Kit_ClearBuffer(dec->buffer[KIT_DEC_OUT]);
+ SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]);
+ }
+}
+
+void Kit_ClearDecoderInput(Kit_Decoder *dec) {
+ if(SDL_LockMutex(dec->lock[KIT_DEC_IN]) == 0) {
+ Kit_ClearBuffer(dec->buffer[KIT_DEC_IN]);
+ SDL_UnlockMutex(dec->lock[KIT_DEC_IN]);
+ }
+}
+
+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_ForEachDecoderOutput(Kit_Decoder *dec, Kit_ForEachItemCallback cb, void *userdata) {
+ assert(dec != NULL);
+ if(SDL_LockMutex(dec->lock[KIT_DEC_OUT]) == 0) {
+ Kit_ForEachItemInBuffer(dec->buffer[KIT_DEC_OUT], cb, userdata);
+ SDL_UnlockMutex(dec->lock[KIT_DEC_OUT]);
+ }
+}
+
+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;
+ Kit_ClearDecoderInput(dec);
+ Kit_ClearDecoderOutput(dec);
+ avcodec_flush_buffers(dec->codec_ctx);
+}
+
+int Kit_RunDecoder(Kit_Decoder *dec) {
+ if(dec == NULL) return 0;
+
+ AVPacket *in_packet;
+ int is_output_full = 1;
+ 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/kitlibstate.c b/src/internal/kitlibstate.c
index 0dd4e6b..d2d98ca 100644
--- a/src/kitlibstate.c
+++ b/src/internal/kitlibstate.c
@@ -1,6 +1,7 @@
+#include <stdlib.h>
#include "kitchensink/internal/kitlibstate.h"
-static Kit_LibraryState _librarystate = {0, NULL};
+static Kit_LibraryState _librarystate = {0, NULL, NULL};
Kit_LibraryState* Kit_GetLibraryState() {
return &_librarystate;
diff --git a/src/internal/libass.c b/src/internal/libass.c
new file mode 100644
index 0000000..a13e32d
--- /dev/null
+++ b/src/internal/libass.c
@@ -0,0 +1,24 @@
+#ifdef USE_DYNAMIC_LIBASS
+
+#include <SDL2/SDL_loadso.h>
+#include "kitchensink/internal/libass.h"
+
+int load_libass(void *handle) {
+ ass_library_init = SDL_LoadFunction(handle, "ass_library_init");
+ ass_library_done = SDL_LoadFunction(handle, "ass_library_done");
+ ass_set_message_cb = SDL_LoadFunction(handle, "ass_set_message_cb");
+ ass_renderer_init = SDL_LoadFunction(handle, "ass_renderer_init");
+ ass_renderer_done = SDL_LoadFunction(handle, "ass_renderer_done");
+ ass_set_frame_size = SDL_LoadFunction(handle, "ass_set_frame_size");
+ ass_set_hinting = SDL_LoadFunction(handle, "ass_set_hinting");
+ ass_set_fonts = SDL_LoadFunction(handle, "ass_set_fonts");
+ ass_render_frame = SDL_LoadFunction(handle, "ass_render_frame");
+ ass_new_track = SDL_LoadFunction(handle, "ass_new_track");
+ ass_free_track = SDL_LoadFunction(handle, "ass_free_track");
+ ass_process_data = SDL_LoadFunction(handle, "ass_process_data");
+ ass_add_font = SDL_LoadFunction(handle, "ass_add_font");
+ ass_process_codec_private = SDL_LoadFunction(handle, "ass_process_codec_private");
+ return 0;
+}
+
+#endif // USE_DYNAMIC_LIBASS
diff --git a/src/internal/subtitle/kitsubtitle.c b/src/internal/subtitle/kitsubtitle.c
new file mode 100644
index 0000000..38d4768
--- /dev/null
+++ b/src/internal/subtitle/kitsubtitle.c
@@ -0,0 +1,254 @@
+#include <assert.h>
+
+#include <SDL2/SDL.h>
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/utils/kitlog.h"
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/kitlib.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/kitsubtitle.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubimage.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubass.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+
+
+#define KIT_SUBTITLE_OUT_SIZE 1024
+
+typedef struct Kit_SubtitleDecoder {
+ Kit_SubtitleFormat *format;
+ Kit_SubtitleRenderer *renderer;
+ AVSubtitle scratch_frame;
+ int w;
+ int h;
+ int output_state;
+ SDL_Surface *tmp_buffer;
+} Kit_SubtitleDecoder;
+
+
+static void free_out_subtitle_packet_cb(void *packet) {
+ Kit_FreeSubtitlePacket((Kit_SubtitlePacket*)packet);
+}
+
+static int dec_decode_subtitle_cb(Kit_Decoder *dec, AVPacket *in_packet) {
+ assert(dec != NULL);
+ assert(in_packet != NULL);
+
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ int frame_finished;
+ int len;
+
+ if(in_packet->size > 0) {
+ len = avcodec_decode_subtitle2(dec->codec_ctx, &subtitle_dec->scratch_frame, &frame_finished, in_packet);
+ if(len < 0) {
+ return 1;
+ }
+
+ if(frame_finished) {
+ // Start and end presentation timestamps for subtitle frame
+ double pts = 0;
+ if(in_packet->pts != AV_NOPTS_VALUE) {
+ pts = in_packet->pts;
+ pts *= av_q2d(dec->format_ctx->streams[dec->stream_index]->time_base);
+ }
+ double start = pts + (subtitle_dec->scratch_frame.start_display_time / 1000.0f);
+ double end = -1;
+ if(subtitle_dec->scratch_frame.end_display_time < UINT_MAX) {
+ end = pts + (subtitle_dec->scratch_frame.end_display_time / 1000.0f);
+ }
+
+ // Create a packet. This should be filled by renderer.
+ Kit_SubtitlePacket *out_packet = Kit_RunSubtitleRenderer(
+ subtitle_dec->renderer, &subtitle_dec->scratch_frame, start, end);
+ if(end < 0) {
+ Kit_ClearDecoderOutput(dec);
+ }
+ if(out_packet != NULL) {
+ Kit_WriteDecoderOutput(dec, out_packet);
+ }
+
+ // Free subtitle since it has now been handled
+ avsubtitle_free(&subtitle_dec->scratch_frame);
+ }
+ }
+
+ return 1;
+}
+
+static void dec_close_subtitle_cb(Kit_Decoder *dec) {
+ if(dec == NULL) return;
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+ SDL_FreeSurface(subtitle_dec->tmp_buffer);
+ Kit_CloseSubtitleRenderer(subtitle_dec->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;
+ }
+
+ Kit_LibraryState *library_state = Kit_GetLibraryState();
+
+ // 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;
+ }
+
+ // Set format. Note that is_enabled may be changed below ...
+ format->is_enabled = true;
+ format->stream_index = src->subtitle_stream_index;
+ format->format = SDL_PIXELFORMAT_RGBA32; // Always this
+
+ // ... then allocate the subtitle decoder
+ Kit_SubtitleDecoder *subtitle_dec = calloc(1, sizeof(Kit_SubtitleDecoder));
+ if(subtitle_dec == NULL) {
+ goto exit_1;
+ }
+
+ // For subtitles, we need a renderer for the stream. Pick one based on codec ID.
+ Kit_SubtitleRenderer *ren = NULL;
+ switch(dec->codec_ctx->codec_id) {
+ case AV_CODEC_ID_TEXT:
+ case AV_CODEC_ID_HDMV_TEXT_SUBTITLE:
+ case AV_CODEC_ID_SRT:
+ case AV_CODEC_ID_SUBRIP:
+ case AV_CODEC_ID_SSA:
+ case AV_CODEC_ID_ASS:
+ if(library_state->init_flags & KIT_INIT_ASS) {
+ ren = Kit_CreateASSSubtitleRenderer(dec, w, h);
+ } else {
+ format->is_enabled = false;
+ }
+ break;
+ case AV_CODEC_ID_DVD_SUBTITLE:
+ case AV_CODEC_ID_DVB_SUBTITLE:
+ case AV_CODEC_ID_HDMV_PGS_SUBTITLE:
+ case AV_CODEC_ID_XSUB:
+ ren = Kit_CreateImageSubtitleRenderer(dec, w, h);
+ break;
+ default:
+ format->is_enabled = false;
+ break;
+ }
+ if(ren == NULL) {
+ Kit_SetError("Unrecognized subtitle format");
+ goto exit_2;
+ }
+
+ // Allocate temporary screen-sized subtitle buffer
+ SDL_Surface *tmp_buffer = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);
+ if(tmp_buffer == NULL) {
+ Kit_SetError("Unable to allocate subtitle buffer");
+ goto exit_3;
+ }
+ SDL_FillRect(tmp_buffer, NULL, 0);
+
+ // Set callbacks and userdata, and we're go
+ subtitle_dec->format = format;
+ subtitle_dec->renderer = ren;
+ subtitle_dec->w = w;
+ subtitle_dec->h = h;
+ subtitle_dec->tmp_buffer = tmp_buffer;
+ subtitle_dec->output_state = 0;
+ dec->dec_decode = dec_decode_subtitle_cb;
+ dec->dec_close = dec_close_subtitle_cb;
+ dec->userdata = subtitle_dec;
+ return dec;
+
+exit_3:
+ Kit_CloseSubtitleRenderer(ren);
+exit_2:
+ free(subtitle_dec);
+exit_1:
+ Kit_CloseDecoder(dec);
+exit_0:
+ return NULL;
+}
+
+
+typedef struct {
+ double sync_ts;
+ SDL_Surface *surface;
+ int rendered;
+} tmp_sub_image;
+
+
+static void _merge_subtitle_texture(void *ptr, void *userdata) {
+ tmp_sub_image *img = userdata;
+ Kit_SubtitlePacket *packet = ptr;
+
+ // Make sure current time is within presentation range
+ if(packet->pts_start >= img->sync_ts)
+ return;
+ if(packet->pts_end <= img->sync_ts && packet->pts_end >= 0)
+ return;
+
+ // Tell the renderer function that we did something here
+ img->rendered = 1;
+
+ // Blit source whole source surface to target surface in requested coords
+ SDL_Rect dst_rect;
+ dst_rect.x = packet->x;
+ dst_rect.y = packet->y;
+ SDL_BlitSurface(packet->surface, NULL, img->surface, &dst_rect);
+}
+
+
+int Kit_GetSubtitleDecoderDataTexture(Kit_Decoder *dec, SDL_Texture *texture) {
+ assert(dec != NULL);
+ assert(texture != NULL);
+
+ Kit_SubtitleDecoder *subtitle_dec = dec->userdata;
+
+ double sync_ts = _GetSystemTime() - dec->clock_sync;
+
+ // If we rendered on last frame, clear the buffer
+ if(subtitle_dec->output_state == 1) {
+ SDL_FillRect(subtitle_dec->tmp_buffer, NULL, 0);
+ }
+
+ // Clear out old packets
+ Kit_SubtitlePacket *packet = NULL;
+ while((packet = Kit_PeekDecoderOutput(dec)) != NULL) {
+ if(packet->pts_end >= sync_ts)
+ break;
+ if(packet->pts_end < 0)
+ break;
+ Kit_AdvanceDecoderOutput(dec);
+ free_out_subtitle_packet_cb(packet);
+ }
+
+ // Blit all subtitle image rectangles to master buffer
+ tmp_sub_image img;
+ img.sync_ts = sync_ts;
+ img.surface = subtitle_dec->tmp_buffer;
+ img.rendered = 0;
+ Kit_ForEachDecoderOutput(dec, _merge_subtitle_texture, (void*)&img);
+
+ // If nothing was rendered now or last frame, just return. No need to update texture.
+ dec->clock_pos = sync_ts;
+ if(img.rendered == 0 && subtitle_dec->output_state == 0) {
+ return 1;
+ }
+ subtitle_dec->output_state = img.rendered;
+
+ // Update output texture with current buffered image
+ SDL_UpdateTexture(
+ texture, NULL,
+ subtitle_dec->tmp_buffer->pixels,
+ subtitle_dec->tmp_buffer->pitch);
+
+ // all done!
+ return 0;
+}
diff --git a/src/internal/subtitle/kitsubtitlepacket.c b/src/internal/subtitle/kitsubtitlepacket.c
new file mode 100644
index 0000000..ff52d39
--- /dev/null
+++ b/src/internal/subtitle/kitsubtitlepacket.c
@@ -0,0 +1,19 @@
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+
+
+Kit_SubtitlePacket* Kit_CreateSubtitlePacket(
+ double pts_start, double pts_end, int pos_x, int pos_y, SDL_Surface *surface)
+{
+ Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket));
+ p->pts_start = pts_start;
+ p->pts_end = pts_end;
+ p->x = pos_x;
+ p->y = pos_y;
+ p->surface = surface;
+ return p;
+}
+
+void Kit_FreeSubtitlePacket(Kit_SubtitlePacket *p) {
+ SDL_FreeSurface(p->surface);
+ free(p);
+}
diff --git a/src/internal/subtitle/renderers/kitsubass.c b/src/internal/subtitle/renderers/kitsubass.c
new file mode 100644
index 0000000..2a9eedf
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubass.c
@@ -0,0 +1,217 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include <SDL2/SDL_surface.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubass.h"
+
+// For compatibility
+#ifndef ASS_FONTPROVIDER_AUTODETECT
+#define ASS_FONTPROVIDER_AUTODETECT 1
+#endif
+
+typedef struct Kit_ASSSubtitleRenderer {
+ ASS_Renderer *renderer;
+ ASS_Track *track;
+} Kit_ASSSubtitleRenderer;
+
+static void _ProcessAssImage(SDL_Surface *surface, const ASS_Image *img, int min_x, int min_y) {
+ unsigned char r = ((img->color) >> 24) & 0xFF;
+ unsigned char g = ((img->color) >> 16) & 0xFF;
+ unsigned char b = ((img->color) >> 8) & 0xFF;
+ unsigned char a = (img->color) & 0xFF;
+ unsigned char *src = img->bitmap;
+ unsigned char *dst = surface->pixels;
+ unsigned int pos_x = img->dst_x - min_x;
+ unsigned int pos_y = img->dst_y - min_y;
+ unsigned int an, ao, x, y, x_off;
+ dst += pos_y * surface->pitch;
+
+ for(y = 0; y < img->h; y++) {
+ for(x = 0; x < img->w; x++) {
+ x_off = (pos_x + x) * 4;
+ an = ((255 - a) * src[x]) >> 8; // New alpha
+ ao = dst[x_off + 3]; // Original alpha
+ if(ao == 0) {
+ dst[x_off + 0] = r;
+ dst[x_off + 1] = g;
+ dst[x_off + 2] = b;
+ dst[x_off + 3] = an;
+ } else {
+ dst[x_off + 3] = 255 - (255 - dst[x_off + 3]) * (255 - an) / 255;
+ if(dst[x_off + 3] != 0) {
+ dst[x_off + 0] = (dst[x_off + 0] * ao * (255-an) / 255 + r * an ) / dst[x_off + 3];
+ dst[x_off + 1] = (dst[x_off + 1] * ao * (255-an) / 255 + g * an ) / dst[x_off + 3];
+ dst[x_off + 2] = (dst[x_off + 2] * ao * (255-an) / 255 + b * an ) / dst[x_off + 3];
+ }
+ }
+ }
+ src += img->stride;
+ dst += surface->pitch;
+ }
+}
+
+static Kit_SubtitlePacket* ren_render_ass_cb(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts) {
+ assert(ren != NULL);
+ assert(src != NULL);
+
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ AVSubtitle *sub = src;
+ SDL_Surface *surface = NULL;
+ ASS_Image *image = NULL;
+ ASS_Image *wt_image = NULL;
+ unsigned int now = start_pts * 1000;
+ int change = 0;
+ int x0 = INT_MAX, y0 = INT_MAX;
+ int x1 = 0, y1 = 0;
+ int w, h;
+
+ // Read incoming subtitle packets to libASS
+ for(int r = 0; r < sub->num_rects; r++) {
+ if(sub->rects[r]->ass == NULL)
+ continue;
+ ass_process_data(ass_ren->track, sub->rects[r]->ass, strlen(sub->rects[r]->ass));
+ }
+
+ // Ask libass to render frames. If there are no changes since last render, stop here.
+ wt_image = image = ass_render_frame(ass_ren->renderer, ass_ren->track, now, &change);
+ if(change == 0) {
+ return NULL;
+ }
+
+ // Find dimensions
+ for(image = wt_image; image; image = image->next) {
+ if(image->dst_x < x0)
+ x0 = image->dst_x;
+ if(image->dst_y < y0)
+ y0 = image->dst_y;
+ if(image->dst_x + image->w > x1)
+ x1 = image->dst_x + image->w;
+ if(image->dst_y + image->h > y1)
+ y1 = image->dst_y + image->h;
+ }
+ w = x1 - x0;
+ h = y1 - y0;
+
+ // Surface to render on
+ surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);
+ SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
+ SDL_FillRect(surface, NULL, 0);
+
+ // Render subimages to the target surface.
+ for(image = wt_image; image; image = image->next) {
+ if(image->w == 0 || image->h == 0)
+ continue;
+ _ProcessAssImage(surface, image, x0, y0);
+ }
+
+ // We tell subtitle handler to clear output before adding this frame.
+ return Kit_CreateSubtitlePacket(start_pts, end_pts, x0, y0, surface);
+}
+
+static void ren_close_ass_cb(Kit_SubtitleRenderer *ren) {
+ if(ren == NULL) return;
+
+ Kit_ASSSubtitleRenderer *ass_ren = ren->userdata;
+ ass_renderer_done(ass_ren->renderer);
+ free(ass_ren);
+}
+
+Kit_SubtitleRenderer* Kit_CreateASSSubtitleRenderer(const Kit_Decoder *dec, int w, int h) {
+ assert(dec != NULL);
+ assert(w >= 0);
+ assert(h >= 0);
+
+ // Make sure that libass library has been initialized + get handle
+ Kit_LibraryState *state = Kit_GetLibraryState();
+ if(state->libass_handle == NULL) {
+ Kit_SetError("Libass library has not been initialized");
+ return NULL;
+ }
+
+ // First allocate the generic decoder component
+ Kit_SubtitleRenderer *ren = Kit_CreateSubtitleRenderer();
+ if(ren == NULL) {
+ goto exit_0;
+ }
+
+ // Next, allocate ASS subtitle renderer context.
+ Kit_ASSSubtitleRenderer *ass_ren = calloc(1, sizeof(Kit_ASSSubtitleRenderer));
+ if(ass_ren == NULL) {
+ goto exit_1;
+ }
+
+ // Initialize libass renderer
+ ASS_Renderer *ass_renderer = ass_renderer_init(state->libass_handle);
+ if(ass_renderer == NULL) {
+ Kit_SetError("Unable to initialize libass renderer");
+ goto exit_2;
+ }
+
+ // Read fonts from attachment streams and give them to libass
+ for(int j = 0; j < dec->format_ctx->nb_streams; j++) {
+ AVStream *st = dec->format_ctx->streams[j];
+ if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) {
+ const AVDictionaryEntry *tag = av_dict_get(
+ st->metadata,
+ "filename",
+ NULL,
+ AV_DICT_MATCH_CASE);
+ if(tag) {
+ ass_add_font(
+ state->libass_handle,
+ tag->value,
+ (char*)st->codec->extradata,
+ st->codec->extradata_size);
+ }
+ }
+ }
+
+ // Init libass fonts and window frame size
+ ass_set_fonts(
+ ass_renderer,
+ NULL, "sans-serif",
+ ASS_FONTPROVIDER_AUTODETECT,
+ NULL, 1);
+ ass_set_frame_size(ass_renderer, w, h);
+ ass_set_hinting(ass_renderer, ASS_HINTING_NONE);
+
+ // Initialize libass track
+ ASS_Track *ass_track = ass_new_track(state->libass_handle);
+ if(ass_track == NULL) {
+ Kit_SetError("Unable to initialize libass track");
+ goto exit_3;
+ }
+
+ // Set up libass track headers (ffmpeg provides these)
+ if(dec->codec_ctx->subtitle_header) {
+ ass_process_codec_private(
+ ass_track,
+ (char*)dec->codec_ctx->subtitle_header,
+ dec->codec_ctx->subtitle_header_size);
+ }
+
+ LOG("kekekekee\n");
+
+ // Set callbacks and userdata, and we're go
+ ass_ren->renderer = ass_renderer;
+ ass_ren->track = ass_track;
+ ren->ren_render = ren_render_ass_cb;
+ ren->ren_close = ren_close_ass_cb;
+ ren->userdata = ass_ren;
+ return ren;
+
+exit_3:
+ ass_renderer_done(ass_renderer);
+exit_2:
+ free(ass_ren);
+exit_1:
+ Kit_CloseSubtitleRenderer(ren);
+exit_0:
+ return NULL;
+}
diff --git a/src/internal/subtitle/renderers/kitsubimage.c b/src/internal/subtitle/renderers/kitsubimage.c
new file mode 100644
index 0000000..4cd5b52
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubimage.c
@@ -0,0 +1,93 @@
+#include <assert.h>
+#include <stdlib.h>
+
+#include <SDL2/SDL_surface.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/utils/kitlog.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubimage.h"
+
+
+static void _ProcessSubImage(SDL_Surface *surface, const AVSubtitleRect *rect, int min_x, int min_y) {
+ SDL_Surface *src = SDL_CreateRGBSurfaceWithFormatFrom(
+ rect->data[0], rect->w, rect->h, 8, rect->linesize[0], SDL_PIXELFORMAT_INDEX8);
+ SDL_SetPaletteColors(src->format->palette, (SDL_Color*)rect->data[1], 0, 256);
+
+ SDL_Rect dst_rect;
+ dst_rect.x = rect->x - min_x;
+ dst_rect.y = rect->y - min_y;
+
+ SDL_BlitSurface(src, NULL, surface, &dst_rect);
+}
+
+static Kit_SubtitlePacket* ren_render_image_cb(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts) {
+ assert(ren != NULL);
+ assert(src != NULL);
+
+ AVSubtitle *sub = src;
+ SDL_Surface *surface = NULL;
+ int x0 = INT_MAX, y0 = INT_MAX;
+ int x1 = 0, y1 = 0;
+ int w, h;
+ int has_content = 0;
+
+ // Find sizes of incoming subtitle bitmaps
+ for(int n = 0; n < sub->num_rects; n++) {
+ AVSubtitleRect *r = sub->rects[n];
+ if(r->type != SUBTITLE_BITMAP)
+ continue;
+ has_content = 1;
+ if(r->x < x0)
+ x0 = r->x;
+ if(r->y < y0)
+ y0 = r->y;
+ if(r->x + r->w > x1)
+ x1 = r->x + r->w;
+ if(r->y + r->h > y1)
+ y1 = r->y + r->h;
+ }
+
+ if(has_content == 0) {
+ return NULL;
+ }
+
+ w = x1 - x0;
+ h = y1 - y0;
+ LOG("x, y = %d, %d w, h = %d, %d\n", x0, y0, w, h);
+
+ // Surface to render on
+ surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SDL_PIXELFORMAT_RGBA32);
+ SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
+ SDL_FillRect(surface, NULL, 0);
+
+ // Render subimages to the target surface.
+ for(int n = 0; n < sub->num_rects; n++) {
+ AVSubtitleRect *r = sub->rects[n];
+ if(r->type != SUBTITLE_BITMAP)
+ continue;
+ _ProcessSubImage(surface, r, x0, y0);
+ }
+
+ LOG("Setting %f, %f\n", start_pts, end_pts);
+ return Kit_CreateSubtitlePacket(start_pts, end_pts, x0, y0, surface);
+}
+
+
+Kit_SubtitleRenderer* Kit_CreateImageSubtitleRenderer(const Kit_Decoder *dec, int w, int h) {
+ assert(dec != NULL);
+ assert(w >= 0);
+ assert(h >= 0);
+
+ // Allocate a new renderer
+ Kit_SubtitleRenderer *ren = Kit_CreateSubtitleRenderer();
+ if(ren == NULL) {
+ return NULL;
+ }
+
+ // Only renderer required, no other data.
+ ren->ren_render = ren_render_image_cb;
+ ren->ren_close = NULL;
+ ren->userdata = NULL;
+ return ren;
+}
diff --git a/src/internal/subtitle/renderers/kitsubrenderer.c b/src/internal/subtitle/renderers/kitsubrenderer.c
new file mode 100644
index 0000000..ce3e408
--- /dev/null
+++ b/src/internal/subtitle/renderers/kitsubrenderer.c
@@ -0,0 +1,33 @@
+#include <stdlib.h>
+#include <assert.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/subtitle/kitsubtitlepacket.h"
+#include "kitchensink/internal/subtitle/renderers/kitsubrenderer.h"
+
+
+Kit_SubtitleRenderer* Kit_CreateSubtitleRenderer() {
+ // Allocate renderer and make sure allocation was a success
+ Kit_SubtitleRenderer *ren = calloc(1, sizeof(Kit_SubtitleRenderer));
+ if(ren == NULL) {
+ Kit_SetError("Unable to allocate kit subtitle renderer");
+ return NULL;
+ }
+ return ren;
+}
+
+Kit_SubtitlePacket* Kit_RunSubtitleRenderer(Kit_SubtitleRenderer *ren, void *src, double start_pts, double end_pts) {
+ if(ren == NULL)
+ return NULL;
+ if(ren->ren_render != NULL)
+ return ren->ren_render(ren, src, start_pts, end_pts);
+ return NULL;
+}
+
+void Kit_CloseSubtitleRenderer(Kit_SubtitleRenderer *ren) {
+ if(ren == NULL)
+ return;
+ if(ren->ren_close != NULL)
+ ren->ren_close(ren);
+ free(ren);
+}
diff --git a/src/kitbuffer.c b/src/internal/utils/kitbuffer.c
index 5c492ea..0133154 100644
--- a/src/kitbuffer.c
+++ b/src/internal/utils/kitbuffer.c
@@ -1,8 +1,8 @@
-#include "kitchensink/internal/kitbuffer.h"
-
#include <stdlib.h>
#include <assert.h>
+#include "kitchensink/internal/utils/kitbuffer.h"
+
Kit_Buffer* Kit_CreateBuffer(unsigned int size, Kit_BufferFreeCallback free_cb) {
Kit_Buffer *b = calloc(1, sizeof(Kit_Buffer));
if(b == NULL) {
@@ -27,6 +27,8 @@ void Kit_DestroyBuffer(Kit_Buffer *buffer) {
void Kit_ClearBuffer(Kit_Buffer *buffer) {
void *data;
+ if(buffer->free_cb == NULL)
+ return;
while((data = Kit_ReadBuffer(buffer)) != NULL) {
buffer->free_cb(data);
}
@@ -47,12 +49,15 @@ void* Kit_ReadBuffer(Kit_Buffer *buffer) {
return NULL;
}
-KIT_LOCAL void* Kit_PeekBuffer(const Kit_Buffer *buffer) {
+void* Kit_PeekBuffer(const Kit_Buffer *buffer) {
assert(buffer != NULL);
- return buffer->data[buffer->read_p % buffer->size];
+ if(buffer->read_p < buffer->write_p) {
+ return buffer->data[buffer->read_p % buffer->size];
+ }
+ return NULL;
}
-KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
+void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
assert(buffer != NULL);
if(buffer->read_p < buffer->write_p) {
buffer->data[buffer->read_p % buffer->size] = NULL;
@@ -64,6 +69,18 @@ KIT_LOCAL void Kit_AdvanceBuffer(Kit_Buffer *buffer) {
}
}
+void Kit_ForEachItemInBuffer(const Kit_Buffer *buffer, Kit_ForEachItemCallback cb, void *userdata) {
+ unsigned int read_p = buffer->read_p;
+ unsigned int write_p = buffer->write_p;
+ while(read_p < write_p) {
+ cb(buffer->data[read_p++ % buffer->size], userdata);
+ if(read_p >= buffer->size) {
+ read_p = read_p % buffer->size;
+ write_p = write_p % buffer->size;
+ }
+ }
+}
+
int Kit_WriteBuffer(Kit_Buffer *buffer, void *ptr) {
assert(buffer != NULL);
assert(ptr != NULL);
diff --git a/src/internal/utils/kithelpers.c b/src/internal/utils/kithelpers.c
new file mode 100644
index 0000000..c68f1c7
--- /dev/null
+++ b/src/internal/utils/kithelpers.c
@@ -0,0 +1,30 @@
+#include <libavutil/time.h>
+#include <libavutil/avstring.h>
+
+#include "kitchensink/internal/utils/kithelpers.h"
+
+static const char * const font_mime[] = {
+ "application/x-font-ttf",
+ "application/x-font-truetype",
+ "application/x-truetype-font",
+ "application/x-font-opentype",
+ "application/vnd.ms-opentype",
+ "application/font-sfnt",
+ NULL
+};
+
+double _GetSystemTime() {
+ return (double)av_gettime() / 1000000.0;
+}
+
+bool attachment_is_font(AVStream *stream) {
+ AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE);
+ if(tag) {
+ for(int n = 0; font_mime[n]; n++) {
+ if(av_strcasecmp(font_mime[n], tag->value) == 0) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
diff --git a/src/kitringbuffer.c b/src/internal/utils/kitringbuffer.c
index 1711d52..70fd24f 100644
--- a/src/kitringbuffer.c
+++ b/src/internal/utils/kitringbuffer.c
@@ -1,17 +1,17 @@
/*
* Ringbuffer
*
- * Copyright (c) 2016, Tuomas Virtanen
+ * Copyright (c) 2017, Tuomas Virtanen
* license: MIT; see LICENSE for details.
*/
-#include "kitchensink/internal/kitringbuffer.h"
-
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
+#include "kitchensink/internal/utils/kitringbuffer.h"
+
/**
* Creates a new ringbuffer with the given size.
* @param size Size for the new ringbuffer
diff --git a/src/internal/video/kitvideo.c b/src/internal/video/kitvideo.c
new file mode 100644
index 0000000..631ff35
--- /dev/null
+++ b/src/internal/video/kitvideo.c
@@ -0,0 +1,269 @@
+#include <assert.h>
+
+#include <libavformat/avformat.h>
+#include <libavutil/imgutils.h>
+#include <libswscale/swscale.h>
+
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/kitdecoder.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitbuffer.h"
+#include "kitchensink/internal/video/kitvideo.h"
+#include "kitchensink/internal/utils/kitlog.h"
+
+#define KIT_VIDEO_OUT_SIZE 2
+#define KIT_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 unsigned int _FindPixelFormat(enum AVPixelFormat 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:
+ return SDL_PIXELFORMAT_YV12;
+ case AV_PIX_FMT_YUYV422:
+ return SDL_PIXELFORMAT_YUY2;
+ case AV_PIX_FMT_UYVY422:
+ return SDL_PIXELFORMAT_UYVY;
+ default:
+ return SDL_PIXELFORMAT_RGBA32;
+ }
+}
+
+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_ARGB32: return AV_PIX_FMT_BGRA;
+ case SDL_PIXELFORMAT_RGBA32: 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 presentation timestamp
+ double 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;
+ format->format = _FindPixelFormat(dec->codec_ctx->pix_fmt);
+
+ // ... 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 for handling format changes
+ video_dec->sws = sws_getContext(
+ dec->codec_ctx->width, // Source w
+ dec->codec_ctx->height, // Source h
+ dec->codec_ctx->pix_fmt, // Source fmt
+ dec->codec_ctx->width, // Target w
+ dec->codec_ctx->height, // Target h
+ _FindAVPixelFormat(format->format), // Target fmt
+ SWS_BILINEAR,
+ NULL, NULL, NULL);
+ if(video_dec->sws == NULL) {
+ Kit_SetError("Unable to initialize video converter context");
+ goto exit_3;
+ }
+
+ // Set callbacks and userdata, and we're go
+ 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_GetVideoDecoderDataTexture(Kit_Decoder *dec, SDL_Texture *texture) {
+ assert(dec != NULL);
+ assert(texture != 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 + KIT_VIDEO_SYNC_THRESHOLD) {
+ // Video is ahead, don't show yet.
+ return 0;
+ } else if(packet->pts < sync_ts - KIT_VIDEO_SYNC_THRESHOLD) {
+ // Video is lagging, skip until we find a good PTS to continue from.
+ while(packet != NULL) {
+ Kit_AdvanceDecoderOutput(dec);
+ free_out_video_packet_cb(packet);
+ packet = Kit_PeekDecoderOutput(dec);
+ if(packet == NULL) {
+ break;
+ } else {
+ dec->clock_pos = packet->pts;
+ }
+ if(packet->pts > sync_ts - KIT_VIDEO_SYNC_THRESHOLD) {
+ break;
+ }
+ }
+ }
+
+ // If we have no viable packet, just skip
+ if(packet == NULL) {
+ return 0;
+ }
+
+ // Update output texture with current video data.
+ // Take formats into account.
+ switch(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);
+ dec->clock_pos = packet->pts;
+ free_out_video_packet_cb(packet);
+
+ return 0;
+}
diff --git a/src/kiterror.c b/src/kiterror.c
index 2c87414..9ed0081 100644
--- a/src/kiterror.c
+++ b/src/kiterror.c
@@ -1,11 +1,11 @@
-#include "kitchensink/kitchensink.h"
-
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
+#include "kitchensink/kitchensink.h"
+
#define KIT_ERRBUFSIZE 1024
static char _error_available = false;
diff --git a/src/kitlib.c b/src/kitlib.c
index e5f1277..2daf44d 100644
--- a/src/kitlib.c
+++ b/src/kitlib.c
@@ -1,35 +1,63 @@
+#include <assert.h>
+#include <SDL2/SDL_loadso.h>
+
+#include <libavformat/avformat.h>
+
+#include "kitchensink/internal/utils/kitlog.h"
#include "kitchensink/kitchensink.h"
#include "kitchensink/internal/kitlibstate.h"
-#include <libavformat/avformat.h>
-#include <ass/ass.h>
-#include <assert.h>
-// No-op
-void _libass_msg_callback(int level, const char *fmt, va_list va, void *data) {}
+static void _libass_msg_callback(int level, const char *fmt, va_list va, void *data) {}
+
+int Kit_InitASS(Kit_LibraryState *state) {
+#ifdef USE_DYNAMIC_LIBASS
+ state->ass_so_handle = SDL_LoadObject(DYNAMIC_LIBASS_NAME);
+ if(state->ass_so_handle == NULL) {
+ Kit_SetError("Unable to load ASS library");
+ return 1;
+ }
+ load_libass(state->ass_so_handle);
+#endif
+ state->libass_handle = ass_library_init();
+ ass_set_message_cb(state->libass_handle, _libass_msg_callback, NULL);
+ return 0;
+}
+
+void Kit_CloseASS(Kit_LibraryState *state) {
+ ass_library_done(state->libass_handle);
+ state->libass_handle = NULL;
+#ifdef USE_DYNAMIC_LIBASS
+ SDL_UnloadObject(state->ass_so_handle);
+ state->ass_so_handle = NULL;
+#endif
+}
int Kit_Init(unsigned int flags) {
Kit_LibraryState *state = Kit_GetLibraryState();
if(state->init_flags != 0) {
Kit_SetError("Kitchensink is already initialized.");
- return 1;
+ goto exit_0;
}
+ av_register_all();
+
if(flags & KIT_INIT_NETWORK) {
avformat_network_init();
}
- if(flags & KIT_INIT_FORMATS) {
- av_register_all();
+ if(flags & KIT_INIT_ASS) {
+ if(Kit_InitASS(state) != 0) {
+ goto exit_1;
+ }
}
state->init_flags = flags;
+ return 0;
- // Init libass
- state->libass_handle = ass_library_init();
+exit_1:
+ avformat_network_deinit();
- // Make libass message spam go away
- ass_set_message_cb(state->libass_handle, _libass_msg_callback, NULL);
-
- return 0;
+exit_0:
+ return 1;
}
void Kit_Quit() {
@@ -38,9 +66,10 @@ void Kit_Quit() {
if(state->init_flags & KIT_INIT_NETWORK) {
avformat_network_deinit();
}
+ if(state->init_flags & KIT_INIT_ASS) {
+ Kit_CloseASS(state);
+ }
state->init_flags = 0;
-
- ass_library_done(state->libass_handle);
}
void Kit_GetVersion(Kit_Version *version) {
diff --git a/src/kitlist.c b/src/kitlist.c
deleted file mode 100644
index b6d861c..0000000
--- a/src/kitlist.c
+++ /dev/null
@@ -1,79 +0,0 @@
-#include "kitchensink/internal/kitlist.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <assert.h>
-
-Kit_List* Kit_CreateList(unsigned int size, Kit_ListFreeCallback free_cb) {
- Kit_List *m = calloc(1, sizeof(Kit_List));
- if(m == NULL) {
- return NULL;
- }
- m->size = size;
- m->free_cb = free_cb;
- m->data = calloc(size, sizeof(void*));
- if(m->data == NULL) {
- free(m);
- return NULL;
- }
- return m;
-}
-
-void Kit_DestroyList(Kit_List *list) {
- if(list == NULL) return;
- Kit_ClearList(list);
- free(list->data);
- free(list);
-}
-
-void Kit_ClearList(Kit_List *list) {
- assert(list != NULL);
- for(unsigned int i = 0; i < list->size; i++) {
- if(list->data[i] != NULL) {
- list->free_cb(list->data[i]);
- list->data[i] = NULL;
- }
- }
- list->length = 0;
-}
-
-void Kit_RemoveFromList(Kit_List *list, unsigned int iterator) {
- assert(list != NULL);
- list->free_cb(list->data[iterator-1]);
- list->data[iterator-1] = NULL;
- list->length--;
-}
-
-void* Kit_IterateList(const Kit_List *list, unsigned int *iterator) {
- assert(list != NULL);
- assert(iterator != NULL);
- while((*iterator) < list->size) {
- void *ptr = list->data[(*iterator)];
- *iterator += 1;
- if(ptr != NULL) {
- return ptr;
- }
- }
- return NULL;
-}
-
-int Kit_WriteList(Kit_List *list, void *ptr) {
- assert(list != NULL);
- assert(ptr != NULL);
- if(list->length >= list->size) {
- return 1;
- }
- for(unsigned int i = 0; i < list->size; i++) {
- if(list->data[i] == NULL) {
- list->data[i] = ptr;
- list->length++;
- return 0;
- }
- }
- return 1;
-}
-
-int Kit_GetListLength(const Kit_List *list) {
- assert(list != NULL);
- return list->length;
-}
diff --git a/src/kitplayer.c b/src/kitplayer.c
index 9f8a963..7e29252 100644
--- a/src/kitplayer.c
+++ b/src/kitplayer.c
@@ -1,826 +1,64 @@
-#include "kitchensink/kitplayer.h"
-#include "kitchensink/kiterror.h"
-#include "kitchensink/internal/kitbuffer.h"
-#include "kitchensink/internal/kitringbuffer.h"
-#include "kitchensink/internal/kitlist.h"
-#include "kitchensink/internal/kitlibstate.h"
-
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-#include <libswscale/swscale.h>
-#include <libswresample/swresample.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/time.h>
-#include <libavutil/samplefmt.h>
-#include <libavutil/avstring.h>
-#include <libavutil/imgutils.h>
-
-#include <SDL2/SDL.h>
-#include <ass/ass.h>
-
-#include <stdlib.h>
-#include <string.h>
#include <assert.h>
-#include <math.h>
-#define __STDC_FORMAT_MACROS
-#include <inttypes.h>
-
-// For compatibility
-#ifndef ASS_FONTPROVIDER_AUTODETECT
-#define ASS_FONTPROVIDER_AUTODETECT 1
-#endif
-
-// Threshold is in seconds
-#define VIDEO_SYNC_THRESHOLD 0.01
-#define AUDIO_SYNC_THRESHOLD 0.05
-
-// Buffersizes
-#define KIT_VBUFFERSIZE 3
-#define KIT_ABUFFERSIZE 64
-#define KIT_CBUFFERSIZE 8
-#define KIT_SBUFFERSIZE 512
-
-typedef enum Kit_ControlPacketType {
- KIT_CONTROL_SEEK,
- KIT_CONTROL_FLUSH
-} Kit_ControlPacketType;
-
-typedef struct Kit_VideoPacket {
- double pts;
- AVFrame *frame;
-} Kit_VideoPacket;
-
-typedef struct Kit_AudioPacket {
- double pts;
- size_t original_size;
- Kit_RingBuffer *rb;
-} Kit_AudioPacket;
-
-typedef struct Kit_ControlPacket {
- Kit_ControlPacketType type;
- double value1;
-} Kit_ControlPacket;
-
-typedef struct Kit_SubtitlePacket {
- double pts_start;
- double pts_end;
- SDL_Rect *rect;
- SDL_Surface *surface;
- SDL_Texture *texture;
-} Kit_SubtitlePacket;
-
-static int _InitCodecs(Kit_Player *player, const Kit_Source *src) {
- assert(player != NULL);
- assert(src != NULL);
-
- AVCodecContext *acodec_ctx = NULL;
- AVCodecContext *vcodec_ctx = NULL;
- AVCodecContext *scodec_ctx = NULL;
- AVCodec *acodec = NULL;
- AVCodec *vcodec = NULL;
- AVCodec *scodec = NULL;
- AVFormatContext *format_ctx = (AVFormatContext *)src->format_ctx;
-
- // Make sure index seems correct
- if(src->astream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid audio stream index: %d", src->astream_idx);
- goto exit_0;
- } else if(src->astream_idx >= 0) {
- // Find audio decoder
- acodec = avcodec_find_decoder(format_ctx->streams[src->astream_idx]->codec->codec_id);
- if(!acodec) {
- Kit_SetError("No suitable audio decoder found");
- goto exit_0;
- }
-
- // Copy the original audio codec context
- acodec_ctx = avcodec_alloc_context3(acodec);
- if(avcodec_copy_context(acodec_ctx, format_ctx->streams[src->astream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy audio codec context");
- goto exit_0;
- }
-
- // Create an audio decoder context
- if(avcodec_open2(acodec_ctx, acodec, NULL) < 0) {
- Kit_SetError("Unable to allocate audio codec context");
- goto exit_1;
- }
- }
-
- // Make sure index seems correct
- if(src->vstream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid video stream index: %d", src->vstream_idx);
- goto exit_2;
- } else if(src->vstream_idx >= 0) {
- // Find video decoder
- vcodec = avcodec_find_decoder(format_ctx->streams[src->vstream_idx]->codec->codec_id);
- if(!vcodec) {
- Kit_SetError("No suitable video decoder found");
- goto exit_2;
- }
-
- // Copy the original video codec context
- vcodec_ctx = avcodec_alloc_context3(vcodec);
- if(avcodec_copy_context(vcodec_ctx, format_ctx->streams[src->vstream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy video codec context");
- goto exit_2;
- }
-
- // Create a video decoder context
- if(avcodec_open2(vcodec_ctx, vcodec, NULL) < 0) {
- Kit_SetError("Unable to allocate video codec context");
- goto exit_3;
- }
- }
-
- if(src->sstream_idx >= (int)format_ctx->nb_streams) {
- Kit_SetError("Invalid subtitle stream index: %d", src->sstream_idx);
- goto exit_2;
- } else if(src->sstream_idx >= 0) {
- // Find subtitle decoder
- scodec = avcodec_find_decoder(format_ctx->streams[src->sstream_idx]->codec->codec_id);
- if(!scodec) {
- Kit_SetError("No suitable subtitle decoder found");
- goto exit_4;
- }
-
- // Copy the original subtitle codec context
- scodec_ctx = avcodec_alloc_context3(scodec);
- if(avcodec_copy_context(scodec_ctx, format_ctx->streams[src->sstream_idx]->codec) != 0) {
- Kit_SetError("Unable to copy subtitle codec context");
- goto exit_4;
- }
-
- // Create a subtitle decoder context
- if(avcodec_open2(scodec_ctx, scodec, NULL) < 0) {
- Kit_SetError("Unable to allocate subtitle codec context");
- goto exit_5;
- }
- }
-
- player->acodec_ctx = acodec_ctx;
- player->vcodec_ctx = vcodec_ctx;
- player->scodec_ctx = scodec_ctx;
- player->src = src;
- return 0;
-
-exit_5:
- avcodec_free_context(&scodec_ctx);
-exit_4:
- avcodec_close(vcodec_ctx);
-exit_3:
- avcodec_free_context(&vcodec_ctx);
-exit_2:
- avcodec_close(acodec_ctx);
-exit_1:
- avcodec_free_context(&acodec_ctx);
-exit_0:
- return 1;
-}
-
-static int reset_libass_track(Kit_Player *player) {
- AVCodecContext *scodec_ctx = player->scodec_ctx;
-
- if(scodec_ctx == NULL) {
- return 0;
- }
-
- // Flush libass track events
- ass_flush_events(player->ass_track);
- return 0;
-}
-
-static void _FindPixelFormat(enum AVPixelFormat fmt, unsigned int *out_fmt) {
- switch(fmt) {
- case AV_PIX_FMT_YUV420P9:
- case AV_PIX_FMT_YUV420P10:
- case AV_PIX_FMT_YUV420P12:
- case AV_PIX_FMT_YUV420P14:
- case AV_PIX_FMT_YUV420P16:
- case AV_PIX_FMT_YUV420P:
- *out_fmt = SDL_PIXELFORMAT_YV12;
- break;
- case AV_PIX_FMT_YUYV422:
- *out_fmt = SDL_PIXELFORMAT_YUY2;
- break;
- case AV_PIX_FMT_UYVY422:
- *out_fmt = SDL_PIXELFORMAT_UYVY;
- break;
- default:
- *out_fmt = SDL_PIXELFORMAT_ABGR8888;
- break;
- }
-}
-
-static void _FindAudioFormat(enum AVSampleFormat fmt, int *bytes, bool *is_signed, unsigned int *format) {
- switch(fmt) {
- case AV_SAMPLE_FMT_U8:
- *bytes = 1;
- *is_signed = false;
- *format = AUDIO_U8;
- break;
- case AV_SAMPLE_FMT_S16:
- *bytes = 2;
- *is_signed = true;
- *format = AUDIO_S16SYS;
- break;
- case AV_SAMPLE_FMT_S32:
- *bytes = 4;
- *is_signed = true;
- *format = AUDIO_S32SYS;
- break;
- default:
- *bytes = 2;
- *is_signed = true;
- *format = AUDIO_S16SYS;
- break;
- }
-}
-
-static enum AVPixelFormat _FindAVPixelFormat(unsigned int fmt) {
- switch(fmt) {
- case SDL_PIXELFORMAT_IYUV: return AV_PIX_FMT_YUV420P;
- case SDL_PIXELFORMAT_YV12: return AV_PIX_FMT_YUV420P;
- case SDL_PIXELFORMAT_YUY2: return AV_PIX_FMT_YUYV422;
- case SDL_PIXELFORMAT_UYVY: return AV_PIX_FMT_UYVY422;
- case SDL_PIXELFORMAT_ARGB8888: return AV_PIX_FMT_BGRA;
- case SDL_PIXELFORMAT_ABGR8888: return AV_PIX_FMT_RGBA;
- default:
- return AV_PIX_FMT_NONE;
- }
-}
-
-static enum AVSampleFormat _FindAVSampleFormat(int format) {
- switch(format) {
- case AUDIO_U8: return AV_SAMPLE_FMT_U8;
- case AUDIO_S16SYS: return AV_SAMPLE_FMT_S16;
- case AUDIO_S32SYS: return AV_SAMPLE_FMT_S32;
- default:
- return AV_SAMPLE_FMT_NONE;
- }
-}
-
-static unsigned int _FindAVChannelLayout(int channels) {
- switch(channels) {
- case 1: return AV_CH_LAYOUT_MONO;
- case 2: return AV_CH_LAYOUT_STEREO;
- case 4: return AV_CH_LAYOUT_QUAD;
- case 6: return AV_CH_LAYOUT_5POINT1;
- default: return AV_CH_LAYOUT_STEREO_DOWNMIX;
- }
-}
-
-static Kit_VideoPacket* _CreateVideoPacket(AVFrame *frame, double pts) {
- Kit_VideoPacket *p = calloc(1, sizeof(Kit_VideoPacket));
- p->frame = frame;
- p->pts = pts;
- return p;
-}
-static void _FreeVideoPacket(void *ptr) {
- Kit_VideoPacket *packet = ptr;
- av_freep(&packet->frame->data[0]);
- av_frame_free(&packet->frame);
- free(packet);
-}
-
-static Kit_AudioPacket* _CreateAudioPacket(const char* data, size_t len, double pts) {
- Kit_AudioPacket *p = calloc(1, sizeof(Kit_AudioPacket));
- p->rb = Kit_CreateRingBuffer(len);
- Kit_WriteRingBuffer(p->rb, data, len);
- p->pts = pts;
- return p;
-}
-
-static void _FreeAudioPacket(void *ptr) {
- Kit_AudioPacket *packet = ptr;
- Kit_DestroyRingBuffer(packet->rb);
- free(packet);
-}
-
-static Kit_ControlPacket* _CreateControlPacket(Kit_ControlPacketType type, double value1) {
- Kit_ControlPacket *p = calloc(1, sizeof(Kit_ControlPacket));
- p->type = type;
- p->value1 = value1;
- return p;
-}
-
-static void _FreeControlPacket(void *ptr) {
- Kit_ControlPacket *packet = ptr;
- free(packet);
-}
-
-
-static Kit_SubtitlePacket* _CreateSubtitlePacket(double pts_start, double pts_end, SDL_Rect *rect, SDL_Surface *surface) {
- Kit_SubtitlePacket *p = calloc(1, sizeof(Kit_SubtitlePacket));
- p->pts_start = pts_start;
- p->pts_end = pts_end;
- p->surface = surface;
- p->rect = rect;
- p->texture = NULL; // Cached texture
- return p;
-}
-
-static void _FreeSubtitlePacket(void *ptr) {
- Kit_SubtitlePacket *packet = ptr;
- SDL_FreeSurface(packet->surface);
- if(packet->texture) {
- SDL_DestroyTexture(packet->texture);
- }
- free(packet->rect);
- free(packet);
-}
-
-static double _GetSystemTime() {
- return (double)av_gettime() / 1000000.0;
-}
-
-static void _HandleVideoPacket(Kit_Player *player, AVPacket *packet) {
- assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- AVCodecContext *vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- AVFrame *iframe = player->tmp_vframe;
-
- while(packet->size > 0) {
- int len = avcodec_decode_video2(vcodec_ctx, player->tmp_vframe, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- // Target frame
- AVFrame *oframe = av_frame_alloc();
- av_image_alloc(
- oframe->data,
- oframe->linesize,
- vcodec_ctx->width,
- vcodec_ctx->height,
- _FindAVPixelFormat(player->vformat.format),
- 1);
-
- // Scale from source format to target format, don't touch the size
- sws_scale(
- (struct SwsContext *)player->sws,
- (const unsigned char * const *)iframe->data,
- iframe->linesize,
- 0,
- vcodec_ctx->height,
- oframe->data,
- oframe->linesize);
-
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = av_frame_get_best_effort_timestamp(player->tmp_vframe);
- pts *= av_q2d(fmt_ctx->streams[player->src->vstream_idx]->time_base);
- }
-
- // Just seeked, set sync clock & pos.
- if(player->seek_flag == 1) {
- player->vclock_pos = pts;
- player->clock_sync = _GetSystemTime() - pts;
- player->seek_flag = 0;
- }
-
- // Lock, write to audio buffer, unlock
- Kit_VideoPacket *vpacket = _CreateVideoPacket(oframe, pts);
- bool done = false;
- if(SDL_LockMutex(player->vmutex) == 0) {
- if(Kit_WriteBuffer((Kit_Buffer*)player->vbuffer, vpacket) == 0) {
- done = true;
- }
- SDL_UnlockMutex(player->vmutex);
- }
-
- // Unable to write packet, free it.
- if(!done) {
- _FreeVideoPacket(vpacket);
- }
- }
- packet->size -= len;
- packet->data += len;
- }
-}
-
-static void _HandleAudioPacket(Kit_Player *player, AVPacket *packet) {
- assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- int len, len2;
- int dst_linesize;
- int dst_nb_samples, dst_bufsize;
- unsigned char **dst_data;
- AVCodecContext *acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- struct SwrContext *swr = (struct SwrContext *)player->swr;
- AVFrame *aframe = (AVFrame*)player->tmp_aframe;
-
- while(packet->size > 0) {
- len = avcodec_decode_audio4(acodec_ctx, aframe, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- dst_nb_samples = av_rescale_rnd(
- aframe->nb_samples,
- player->aformat.samplerate,
- acodec_ctx->sample_rate,
- AV_ROUND_UP);
-
- av_samples_alloc_array_and_samples(
- &dst_data,
- &dst_linesize,
- player->aformat.channels,
- dst_nb_samples,
- _FindAVSampleFormat(player->aformat.format),
- 0);
-
- len2 = swr_convert(
- swr,
- dst_data,
- aframe->nb_samples,
- (const unsigned char **)aframe->extended_data,
- aframe->nb_samples);
-
- dst_bufsize = av_samples_get_buffer_size(
- &dst_linesize,
- player->aformat.channels,
- len2,
- _FindAVSampleFormat(player->aformat.format), 1);
-
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = av_frame_get_best_effort_timestamp(player->tmp_aframe);
- pts *= av_q2d(fmt_ctx->streams[player->src->astream_idx]->time_base);
- }
-
- // Just seeked, set sync clock & pos.
- if(player->seek_flag == 1) {
- player->vclock_pos = pts;
- player->clock_sync = _GetSystemTime() - pts;
- player->seek_flag = 0;
- }
-
- // Lock, write to audio buffer, unlock
- Kit_AudioPacket *apacket = _CreateAudioPacket((char*)dst_data[0], (size_t)dst_bufsize, pts);
- bool done = false;
- if(SDL_LockMutex(player->amutex) == 0) {
- if(Kit_WriteBuffer((Kit_Buffer*)player->abuffer, apacket) == 0) {
- done = true;
- }
- SDL_UnlockMutex(player->amutex);
- }
-
- // Couldn't write packet, free memory
- if(!done) {
- _FreeAudioPacket(apacket);
- }
-
- av_freep(&dst_data[0]);
- av_freep(&dst_data);
- }
-
- packet->size -= len;
- packet->data += len;
- }
-}
-
-static void _HandleBitmapSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub, AVSubtitleRect *rect) {
- if(rect->nb_colors == 256) {
- // Paletted image based subtitles. Convert and set palette.
- SDL_Surface *s = SDL_CreateRGBSurfaceFrom(
- rect->data[0],
- rect->w, rect->h, 8,
- rect->linesize[0],
- 0, 0, 0, 0);
-
- SDL_SetPaletteColors(s->format->palette, (SDL_Color*)rect->data[1], 0, 256);
-
- Uint32 rmask, gmask, bmask, amask;
- #if SDL_BYTEORDER == SDL_BIG_ENDIAN
- rmask = 0xff000000;
- gmask = 0x00ff0000;
- bmask = 0x0000ff00;
- amask = 0x000000ff;
- #else
- rmask = 0x000000ff;
- gmask = 0x0000ff00;
- bmask = 0x00ff0000;
- amask = 0xff000000;
- #endif
- SDL_Surface *tmp = SDL_CreateRGBSurface(
- 0, rect->w, rect->h, 32,
- rmask, gmask, bmask, amask);
- SDL_BlitSurface(s, NULL, tmp, NULL);
- SDL_FreeSurface(s);
-
- SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect));
- dst_rect->x = rect->x;
- dst_rect->y = rect->y;
- dst_rect->w = rect->w;
- dst_rect->h = rect->h;
-
- double start = pts + (sub->start_display_time / 1000.0f);
- double end = -1;
- if(sub->end_display_time < UINT_MAX) {
- end = pts + (sub->end_display_time / 1000.0f);
- }
-
- spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp);
- }
-}
-
-static void _ProcessAssSubtitleRect(Kit_Player *player, AVSubtitleRect *rect) {
- ass_process_data((ASS_Track*)player->ass_track, rect->ass, strlen(rect->ass));
-}
-
-static void _ProcessAssImage(SDL_Surface *surface, const ASS_Image *img) {
- int x, y;
- // libass headers claim img->color is RGBA, but the alpha is 0.
- unsigned char r = ((img->color) >> 24) & 0xFF;
- unsigned char g = ((img->color) >> 16) & 0xFF;
- unsigned char b = ((img->color) >> 8) & 0xFF;
- unsigned char *src = img->bitmap;
- unsigned char *dst = (unsigned char*)surface->pixels;
-
- for(y = 0; y < img->h; y++) {
- for(x = 0; x < img->w; x++) {
- dst[x * 4 + 0] = r;
- dst[x * 4 + 1] = g;
- dst[x * 4 + 2] = b;
- dst[x * 4 + 3] = src[x];
- }
- src += img->stride;
- dst += surface->pitch;
- }
-}
-
-static void _HandleAssSubtitle(Kit_SubtitlePacket** spackets, int *n, Kit_Player *player, double pts, AVSubtitle *sub) {
- double start = pts + (sub->start_display_time / 1000.0f);
- double end = pts + (sub->end_display_time / 1000.0f);
-
- // Process current chunk of data
- unsigned int now = start * 1000;
- int change = 0;
- ASS_Image *images = ass_render_frame((ASS_Renderer*)player->ass_renderer, (ASS_Track*)player->ass_track, now, &change);
-
- // Convert to SDL_Surfaces
- if(change > 0) {
- ASS_Image *now = images;
- if(now != NULL) {
- do {
- Uint32 rmask, gmask, bmask, amask;
- #if SDL_BYTEORDER == SDL_BIG_ENDIAN
- rmask = 0xff000000;
- gmask = 0x00ff0000;
- bmask = 0x0000ff00;
- amask = 0x000000ff;
- #else
- rmask = 0x000000ff;
- gmask = 0x0000ff00;
- bmask = 0x00ff0000;
- amask = 0xff000000;
- #endif
- SDL_Surface *tmp = SDL_CreateRGBSurface(
- 0, now->w, now->h, 32,
- rmask, gmask, bmask, amask);
-
- _ProcessAssImage(tmp, now);
-
- SDL_Rect *dst_rect = malloc(sizeof(SDL_Rect));
- dst_rect->x = now->dst_x;
- dst_rect->y = now->dst_y;
- dst_rect->w = now->w;
- dst_rect->h = now->h;
-
- spackets[(*n)++] = _CreateSubtitlePacket(start, end, dst_rect, tmp);
- } while((now = now->next) != NULL);
- }
- }
-}
-
-static void _HandleSubtitlePacket(Kit_Player *player, AVPacket *packet) {
- assert(player != NULL);
- assert(packet != NULL);
-
- int frame_finished;
- int len;
- AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx;
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
- Kit_SubtitlePacket *tmp = NULL;
- unsigned int it;
- AVSubtitle sub;
- memset(&sub, 0, sizeof(AVSubtitle));
-
- if(packet->size > 0) {
- len = avcodec_decode_subtitle2(scodec_ctx, &sub, &frame_finished, packet);
- if(len < 0) {
- return;
- }
-
- if(frame_finished) {
- // Get pts
- double pts = 0;
- if(packet->dts != AV_NOPTS_VALUE) {
- pts = packet->pts;
- pts *= av_q2d(fmt_ctx->streams[player->src->sstream_idx]->time_base);
- }
-
- // Convert subtitles to SDL_Surface and create a packet
- Kit_SubtitlePacket *spackets[KIT_SBUFFERSIZE];
- memset(spackets, 0, sizeof(Kit_SubtitlePacket*) * KIT_SBUFFERSIZE);
-
- int n = 0;
- bool has_ass = false;
- for(int r = 0; r < sub.num_rects; r++) {
- switch(sub.rects[r]->type) {
- case SUBTITLE_BITMAP:
- _HandleBitmapSubtitle(spackets, &n, player, pts, &sub, sub.rects[r]);
- break;
- case SUBTITLE_ASS:
- _ProcessAssSubtitleRect(player, sub.rects[r]);
- has_ass = true;
- break;
- case SUBTITLE_TEXT:
- break;
- case SUBTITLE_NONE:
- break;
- }
- }
-
- // Process libass content
- if(has_ass) {
- _HandleAssSubtitle(spackets, &n, player, pts, &sub);
- }
-
- // Lock, write to subtitle buffer, unlock
- if(SDL_LockMutex(player->smutex) == 0) {
- if(has_ass) {
- Kit_ClearList((Kit_List*)player->sbuffer);
- } else {
- // Clear out old subtitles that should only be valid until next (this) subtitle
- it = 0;
- while((tmp = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(tmp->pts_end < 0) {
- Kit_RemoveFromList((Kit_List*)player->sbuffer, it);
- }
- }
- }
-
- // Add new subtitle
- for(int i = 0; i < KIT_SBUFFERSIZE; i++) {
- Kit_SubtitlePacket *spacket = spackets[i];
- if(spacket != NULL) {
- if(Kit_WriteList((Kit_List*)player->sbuffer, spacket) == 0) {
- spackets[i] = NULL;
- }
- }
- }
-
- // Unlock subtitle buffer
- SDL_UnlockMutex(player->smutex);
- }
-
- // Couldn't write packet, free memory
- for(int i = 0; i < KIT_SBUFFERSIZE; i++) {
- if(spackets[i] != NULL) {
- _FreeSubtitlePacket(spackets[i]);
- }
- }
- }
- }
-}
-
-static void _HandlePacket(Kit_Player *player, AVPacket *packet) {
- // Check if this is a packet we need to handle and pass it on
- if(player->vcodec_ctx != NULL && packet->stream_index == player->src->vstream_idx) {
- _HandleVideoPacket(player, packet);
- }
- else if(player->acodec_ctx != NULL && packet->stream_index == player->src->astream_idx) {
- _HandleAudioPacket(player, packet);
- }
- else if(player->scodec_ctx != NULL && packet->stream_index == player->src->sstream_idx) {
- _HandleSubtitlePacket(player, packet);
- }
-}
-
-static void _HandleFlushCommand(Kit_Player *player, Kit_ControlPacket *packet) {
- if(player->abuffer != NULL) {
- if(SDL_LockMutex(player->amutex) == 0) {
- Kit_ClearBuffer((Kit_Buffer*)player->abuffer);
- SDL_UnlockMutex(player->amutex);
- }
- }
- if(player->vbuffer != NULL) {
- if(SDL_LockMutex(player->vmutex) == 0) {
- Kit_ClearBuffer((Kit_Buffer*)player->vbuffer);
- SDL_UnlockMutex(player->vmutex);
- }
- }
- if(player->sbuffer != NULL) {
- if(SDL_LockMutex(player->smutex) == 0) {
- Kit_ClearList((Kit_List*)player->sbuffer);
- SDL_UnlockMutex(player->smutex);
- }
- }
- reset_libass_track(player);
-}
-
-static void _HandleSeekCommand(Kit_Player *player, Kit_ControlPacket *packet) {
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
-
- // Find and limit absolute position
- double seek = packet->value1;
- double duration = Kit_GetPlayerDuration(player);
- if(player->vclock_pos + seek <= 0) {
- seek = -player->vclock_pos;
- }
- if(player->vclock_pos + seek >= duration) {
- seek = duration - player->vclock_pos;
- }
- double absolute_pos = player->vclock_pos + seek;
- int64_t seek_target = absolute_pos * AV_TIME_BASE;
-
- // Seek to timestamp.
- avformat_seek_file(fmt_ctx, -1, INT64_MIN, seek_target, INT64_MAX, 0);
- if(player->vcodec_ctx != NULL)
- avcodec_flush_buffers(player->vcodec_ctx);
- if(player->acodec_ctx != NULL)
- avcodec_flush_buffers(player->acodec_ctx);
-
- // On first packet, set clock and current position
- player->seek_flag = 1;
-}
+#include <SDL2/SDL.h>
-static void _HandleControlPacket(Kit_Player *player, Kit_ControlPacket *packet) {
- switch(packet->type) {
- case KIT_CONTROL_FLUSH:
- _HandleFlushCommand(player, packet);
- break;
- case KIT_CONTROL_SEEK:
- _HandleSeekCommand(player, packet);
- break;
- }
-}
+#include "kitchensink/kitplayer.h"
+#include "kitchensink/kiterror.h"
+#include "kitchensink/internal/kitlibstate.h"
+#include "kitchensink/internal/video/kitvideo.h"
+#include "kitchensink/internal/audio/kitaudio.h"
+#include "kitchensink/internal/subtitle/kitsubtitle.h"
+#include "kitchensink/internal/utils/kithelpers.h"
+#include "kitchensink/internal/utils/kitlog.h"
// Return 0 if stream is good but nothing else to do for now
-// Return -1 if there is still work to be done
+// Return -1 if there may still work to be done
// Return 1 if there was an error or stream end
-static int _UpdatePlayer(Kit_Player *player) {
+static int _DemuxStream(const Kit_Player *player) {
assert(player != NULL);
- AVFormatContext *format_ctx = (AVFormatContext*)player->src->format_ctx;
-
- // Handle control queue
- if(SDL_LockMutex(player->cmutex) == 0) {
- Kit_ControlPacket *cpacket;
- while((cpacket = (Kit_ControlPacket*)Kit_ReadBuffer(player->cbuffer)) != NULL) {
- _HandleControlPacket(player, cpacket);
- _FreeControlPacket(cpacket);
- }
- SDL_UnlockMutex(player->cmutex);
- }
+ AVFormatContext *format_ctx = player->src->format_ctx;
+ Kit_Decoder *decoders[] = {
+ player->video_dec,
+ player->audio_dec,
+ player->subtitle_dec
+ };
- // If either buffer is full, just stop here for now.
+ // If any buffer is full, just stop here for now.
// Since we don't know what kind of data is going to come out of av_read_frame, we really
// want to make sure we are prepared for everything :)
- if(player->vcodec_ctx != NULL) {
- if(SDL_LockMutex(player->vmutex) == 0) {
- int ret = Kit_IsBufferFull(player->vbuffer);
- SDL_UnlockMutex(player->vmutex);
- if(ret == 1) {
- return 0;
- }
- }
- }
- if(player->acodec_ctx != NULL) {
- if(SDL_LockMutex(player->amutex) == 0) {
- int ret = Kit_IsBufferFull(player->abuffer);
- SDL_UnlockMutex(player->amutex);
- if(ret == 1) {
- return 0;
- }
- }
+ for(int i = 0; i < 3; i++) {
+ if(decoders[i] == NULL)
+ continue;
+ if(!Kit_CanWriteDecoderInput(decoders[i]))
+ return 0;
}
// Attempt to read frame. Just return here if it fails.
- AVPacket packet;
- if(av_read_frame(format_ctx, &packet) < 0) {
+ AVPacket *packet = av_packet_alloc();
+ if(av_read_frame(format_ctx, packet) < 0) {
+ av_packet_free(&packet);
return 1;
}
- _HandlePacket(player, &packet);
- av_packet_unref(&packet);
+
+ // Check if this is a packet we need to handle and pass it on
+ for(int i = 0; i < 3; i++) {
+ if(decoders[i] == NULL)
+ continue;
+ if(decoders[i]->stream_index == packet->stream_index) {
+ Kit_WriteDecoderInput(decoders[i], packet);
+ return -1;
+ }
+ }
+
+ // We only get here if packet was not written to a decoder. IF that is the case,
+ // disregard and free the packet.
+ av_packet_free(&packet);
return -1;
}
static int _DecoderThread(void *ptr) {
- Kit_Player *player = (Kit_Player*)ptr;
+ Kit_Player *player = ptr;
bool is_running = true;
bool is_playing = true;
int ret;
@@ -844,12 +82,24 @@ static int _DecoderThread(void *ptr) {
}
// Get more data from demuxer, decode. Wait a bit if there's no more work for now.
- ret = _UpdatePlayer(player);
- if(ret == 1) {
- player->state = KIT_STOPPED;
- } else if(ret == 0) {
- SDL_Delay(1);
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ while((ret = _DemuxStream(player)) == -1);
+ if(ret == 1) {
+ player->state = KIT_STOPPED;
+ continue;
+ }
+
+ // Run decoders for a bit
+ while(Kit_RunDecoder(player->video_dec) == 1);
+ while(Kit_RunDecoder(player->audio_dec) == 1);
+ while(Kit_RunDecoder(player->subtitle_dec) == 1);
+
+ // Free decoder thread lock.
+ SDL_UnlockMutex(player->dec_lock);
}
+
+ // We decoded as much as we could, sleep a bit.
+ SDL_Delay(1);
}
// Just idle while waiting for work.
@@ -859,28 +109,6 @@ static int _DecoderThread(void *ptr) {
return 0;
}
-static const char * const font_mime[] = {
- "application/x-font-ttf",
- "application/x-font-truetype",
- "application/x-truetype-font",
- "application/x-font-opentype",
- "application/vnd.ms-opentype",
- "application/font-sfnt",
- NULL
-};
-
-static bool attachment_is_font(AVStream *stream) {
- AVDictionaryEntry *tag = av_dict_get(stream->metadata, "mimetype", NULL, AV_DICT_MATCH_CASE);
- if(tag) {
- for(int n = 0; font_mime[n]; n++) {
- if(av_strcasecmp(font_mime[n], tag->value) == 0) {
- return true;
- }
- }
- }
- return false;
-}
-
Kit_Player* Kit_CreatePlayer(const Kit_Source *src) {
assert(src != NULL);
@@ -890,293 +118,70 @@ Kit_Player* Kit_CreatePlayer(const Kit_Source *src) {
return NULL;
}
- AVCodecContext *acodec_ctx = NULL;
- AVCodecContext *vcodec_ctx = NULL;
- AVCodecContext *scodec_ctx = NULL;
-
- // Initialize codecs
- if(_InitCodecs(player, src) != 0) {
+ // Initialize audio decoder
+ player->audio_dec = Kit_CreateAudioDecoder(src, &player->aformat);
+ if(player->audio_dec == NULL && src->audio_stream_index >= 0) {
goto error;
}
- // Init audio codec information if audio codec is initialized
- acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- if(acodec_ctx != NULL) {
- player->aformat.samplerate = acodec_ctx->sample_rate;
- player->aformat.channels = acodec_ctx->channels > 2 ? 2 : acodec_ctx->channels;
- player->aformat.is_enabled = true;
- player->aformat.stream_idx = src->astream_idx;
- _FindAudioFormat(acodec_ctx->sample_fmt, &player->aformat.bytes, &player->aformat.is_signed, &player->aformat.format);
-
- player->swr = swr_alloc_set_opts(
- NULL,
- _FindAVChannelLayout(player->aformat.channels), // Target channel layout
- _FindAVSampleFormat(player->aformat.format), // Target fmt
- player->aformat.samplerate, // Target samplerate
- acodec_ctx->channel_layout, // Source channel layout
- acodec_ctx->sample_fmt, // Source fmt
- acodec_ctx->sample_rate, // Source samplerate
- 0, NULL);
- if(swr_init((struct SwrContext *)player->swr) != 0) {
- Kit_SetError("Unable to initialize audio converter context");
- goto error;
- }
-
- player->abuffer = Kit_CreateBuffer(KIT_ABUFFERSIZE, _FreeAudioPacket);
- if(player->abuffer == NULL) {
- Kit_SetError("Unable to initialize audio ringbuffer");
- goto error;
- }
-
- player->tmp_aframe = av_frame_alloc();
- if(player->tmp_aframe == NULL) {
- Kit_SetError("Unable to initialize temporary audio frame");
- goto error;
- }
- }
-
- // Initialize video codec information is initialized
- vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- if(vcodec_ctx != NULL) {
- player->vformat.is_enabled = true;
- player->vformat.width = vcodec_ctx->width;
- player->vformat.height = vcodec_ctx->height;
- player->vformat.stream_idx = src->vstream_idx;
- _FindPixelFormat(vcodec_ctx->pix_fmt, &player->vformat.format);
-
- player->sws = sws_getContext(
- vcodec_ctx->width, // Source w
- vcodec_ctx->height, // Source h
- vcodec_ctx->pix_fmt, // Source fmt
- vcodec_ctx->width, // Target w
- vcodec_ctx->height, // Target h
- _FindAVPixelFormat(player->vformat.format), // Target fmt
- SWS_BICUBIC,
- NULL, NULL, NULL);
- if((struct SwsContext *)player->sws == NULL) {
- Kit_SetError("Unable to initialize video converter context");
- goto error;
- }
-
- player->vbuffer = Kit_CreateBuffer(KIT_VBUFFERSIZE, _FreeVideoPacket);
- if(player->vbuffer == NULL) {
- Kit_SetError("Unable to initialize video ringbuffer");
- goto error;
- }
-
- player->tmp_vframe = av_frame_alloc();
- if(player->tmp_vframe == NULL) {
- Kit_SetError("Unable to initialize temporary video frame");
- goto error;
- }
- }
-
- // Initialize subtitle codec
- scodec_ctx = (AVCodecContext*)player->scodec_ctx;
- if(scodec_ctx != NULL) {
- player->sformat.is_enabled = true;
- player->sformat.stream_idx = src->sstream_idx;
-
- // subtitle packet buffer
- player->sbuffer = Kit_CreateList(KIT_SBUFFERSIZE, _FreeSubtitlePacket);
- if(player->sbuffer == NULL) {
- Kit_SetError("Unable to initialize active subtitle list");
- goto error;
- }
-
- // Initialize libass renderer
- Kit_LibraryState *state = Kit_GetLibraryState();
- player->ass_renderer = ass_renderer_init(state->libass_handle);
- if(player->ass_renderer == NULL) {
- Kit_SetError("Unable to initialize libass renderer");
- goto error;
- }
-
- // Read fonts from attachment streams and give them to libass
- AVFormatContext *format_ctx = player->src->format_ctx;
- for (int j = 0; j < format_ctx->nb_streams; j++) {
- AVStream *st = format_ctx->streams[j];
- if(st->codec->codec_type == AVMEDIA_TYPE_ATTACHMENT && attachment_is_font(st)) {
- const AVDictionaryEntry *tag = av_dict_get(
- st->metadata,
- "filename",
- NULL,
- AV_DICT_MATCH_CASE);
- if(tag) {
- ass_add_font(
- state->libass_handle,
- tag->value,
- (char*)st->codec->extradata,
- st->codec->extradata_size);
- }
- }
- }
-
- // Init libass fonts and window frame size
- ass_set_fonts(player->ass_renderer, NULL, "sans-serif", ASS_FONTPROVIDER_AUTODETECT, NULL, 1);
- ass_set_frame_size(player->ass_renderer, vcodec_ctx->width, vcodec_ctx->height);
- ass_set_hinting(player->ass_renderer, ASS_HINTING_NONE);
-
- // Initialize libass track
- player->ass_track = ass_new_track(state->libass_handle);
- if(player->ass_track == NULL) {
- Kit_SetError("Unable to initialize libass track");
- goto error;
- }
-
- // Set up libass track headers (ffmpeg provides these)
- if(scodec_ctx->subtitle_header) {
- ass_process_codec_private(
- (ASS_Track*)player->ass_track,
- (char*)scodec_ctx->subtitle_header,
- scodec_ctx->subtitle_header_size);
- }
- }
-
- player->cbuffer = Kit_CreateBuffer(KIT_CBUFFERSIZE, _FreeControlPacket);
- if(player->cbuffer == NULL) {
- Kit_SetError("Unable to initialize control ringbuffer");
- goto error;
- }
-
- player->vmutex = SDL_CreateMutex();
- if(player->vmutex == NULL) {
- Kit_SetError("Unable to allocate video mutex");
+ // Initialize video decoder
+ player->video_dec = Kit_CreateVideoDecoder(src, &player->vformat);
+ if(player->video_dec == NULL && src->video_stream_index >= 0) {
goto error;
}
- player->amutex = SDL_CreateMutex();
- if(player->amutex == NULL) {
- Kit_SetError("Unable to allocate audio mutex");
+ // Initialize subtitle decoder
+ player->subtitle_dec = Kit_CreateSubtitleDecoder(
+ src, &player->sformat, player->vformat.width, player->vformat.height);
+ if(player->subtitle_dec == NULL && src->subtitle_stream_index >= 0) {
goto error;
}
- player->cmutex = SDL_CreateMutex();
- if(player->cmutex == NULL) {
- Kit_SetError("Unable to allocate control buffer mutex");
- goto error;
- }
-
- player->smutex = SDL_CreateMutex();
- if(player->smutex == NULL) {
- Kit_SetError("Unable to allocate subtitle buffer mutex");
+ // Decoder thread lock
+ player->dec_lock = SDL_CreateMutex();
+ if(player->dec_lock == NULL) {
+ Kit_SetError("Unable to create a decoder thread lock mutex: %s", SDL_GetError());
goto error;
}
+ // Decoder thread
player->dec_thread = SDL_CreateThread(_DecoderThread, "Kit Decoder Thread", player);
if(player->dec_thread == NULL) {
Kit_SetError("Unable to create a decoder thread: %s", SDL_GetError());
goto error;
}
+ player->src = src;
return player;
error:
- if(player->amutex != NULL) {
- SDL_DestroyMutex(player->amutex);
- }
- if(player->vmutex != NULL) {
- SDL_DestroyMutex(player->vmutex);
- }
- if(player->cmutex != NULL) {
- SDL_DestroyMutex(player->cmutex);
- }
- if(player->smutex != NULL) {
- SDL_DestroyMutex(player->smutex);
- }
- if(player->tmp_aframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_aframe);
- }
- if(player->tmp_vframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_vframe);
- }
-
- Kit_DestroyBuffer((Kit_Buffer*)player->vbuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->abuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->cbuffer);
- Kit_DestroyList((Kit_List*)player->sbuffer);
-
- if(player->sws != NULL) {
- sws_freeContext((struct SwsContext *)player->sws);
- }
- if(player->swr != NULL) {
- swr_free((struct SwrContext **)player->swr);
- }
-
- if(player->ass_track != NULL) {
- ass_free_track((ASS_Track*)player->ass_track);
- }
- if(player->ass_renderer != NULL) {
- ass_renderer_done((ASS_Renderer *)player->ass_renderer);
- }
- if(player != NULL) {
- free(player);
- }
+ Kit_ClosePlayer(player);
return NULL;
}
void Kit_ClosePlayer(Kit_Player *player) {
if(player == NULL) return;
- // Kill the decoder thread
+ // Kill the decoder thread and mutex
player->state = KIT_CLOSED;
SDL_WaitThread(player->dec_thread, NULL);
- SDL_DestroyMutex(player->vmutex);
- SDL_DestroyMutex(player->amutex);
- SDL_DestroyMutex(player->cmutex);
- SDL_DestroyMutex(player->smutex);
-
- // Free up converters
- if(player->sws != NULL) {
- sws_freeContext((struct SwsContext *)player->sws);
- }
- if(player->swr != NULL) {
- swr_free((struct SwrContext **)&player->swr);
- }
-
- // Free temporary frames
- if(player->tmp_vframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_vframe);
- }
- if(player->tmp_aframe != NULL) {
- av_frame_free((AVFrame**)&player->tmp_aframe);
- }
-
- // Free contexts
- avcodec_close((AVCodecContext*)player->acodec_ctx);
- avcodec_close((AVCodecContext*)player->vcodec_ctx);
- avcodec_close((AVCodecContext*)player->scodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->acodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->vcodec_ctx);
- avcodec_free_context((AVCodecContext**)&player->scodec_ctx);
+ SDL_DestroyMutex(player->dec_lock);
- // Free local audio buffers
- Kit_DestroyBuffer((Kit_Buffer*)player->cbuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->abuffer);
- Kit_DestroyBuffer((Kit_Buffer*)player->vbuffer);
- Kit_DestroyList((Kit_List*)player->sbuffer);
-
- // Free libass context
- if(player->ass_track != NULL) {
- ass_free_track((ASS_Track*)player->ass_track);
- }
- if(player->ass_renderer != NULL) {
- ass_renderer_done((ASS_Renderer *)player->ass_renderer);
- }
+ // Shutdown decoders
+ Kit_CloseDecoder(player->audio_dec);
+ Kit_CloseDecoder(player->video_dec);
+ Kit_CloseDecoder(player->subtitle_dec);
// Free the player structure itself
free(player);
}
-int Kit_GetVideoData(Kit_Player *player, SDL_Texture *texture) {
+int Kit_GetVideoDataTexture(Kit_Player *player, SDL_Texture *texture) {
assert(player != NULL);
-
- if(player->src->vstream_idx == -1) {
+ if(player->video_dec == NULL) {
return 0;
}
- assert(texture != NULL);
-
// If paused or stopped, do nothing
if(player->state == KIT_PAUSED) {
return 0;
@@ -1185,80 +190,20 @@ int Kit_GetVideoData(Kit_Player *player, SDL_Texture *texture) {
return 0;
}
- // Read a packet from buffer, if one exists. Stop here if not.
- Kit_VideoPacket *packet = NULL;
- Kit_VideoPacket *n_packet = NULL;
- if(SDL_LockMutex(player->vmutex) == 0) {
- packet = (Kit_VideoPacket*)Kit_PeekBuffer((Kit_Buffer*)player->vbuffer);
- if(packet == NULL) {
- SDL_UnlockMutex(player->vmutex);
- return 0;
- }
-
- // Print some data
- double cur_video_ts = _GetSystemTime() - player->clock_sync;
-
- // Check if we want the packet
- if(packet->pts > cur_video_ts + VIDEO_SYNC_THRESHOLD) {
- // Video is ahead, don't show yet.
- SDL_UnlockMutex(player->vmutex);
- return 0;
- } else if(packet->pts < cur_video_ts - VIDEO_SYNC_THRESHOLD) {
- // Video is lagging, skip until we find a good PTS to continue from.
- while(packet != NULL) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->vbuffer);
- n_packet = (Kit_VideoPacket*)Kit_PeekBuffer((Kit_Buffer*)player->vbuffer);
- if(n_packet == NULL) {
- break;
- }
- _FreeVideoPacket(packet);
- packet = n_packet;
- if(packet->pts > cur_video_ts - VIDEO_SYNC_THRESHOLD) {
- break;
- }
- }
- }
-
- // Advance buffer one frame forwards
- Kit_AdvanceBuffer((Kit_Buffer*)player->vbuffer);
- player->vclock_pos = packet->pts;
-
- // Update textures as required. Handle UYV frames separately.
- if(player->vformat.format == SDL_PIXELFORMAT_YV12
- || player->vformat.format == SDL_PIXELFORMAT_IYUV)
- {
- SDL_UpdateYUVTexture(
- texture, NULL,
- packet->frame->data[0], packet->frame->linesize[0],
- packet->frame->data[1], packet->frame->linesize[1],
- packet->frame->data[2], packet->frame->linesize[2]);
- }
- else {
- SDL_UpdateTexture(
- texture, NULL,
- packet->frame->data[0],
- packet->frame->linesize[0]);
- }
-
- _FreeVideoPacket(packet);
- SDL_UnlockMutex(player->vmutex);
- } else {
- Kit_SetError("Unable to lock video buffer mutex");
- return 1;
- }
-
- return 0;
+ return Kit_GetVideoDecoderDataTexture(player->video_dec, texture);
}
-int Kit_GetSubtitleData(Kit_Player *player, SDL_Renderer *renderer) {
+int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length) {
assert(player != NULL);
-
- // If there is no audio stream, don't bother.
- if(player->src->sstream_idx == -1) {
+ assert(buffer != NULL);
+ if(player->audio_dec == NULL) {
return 0;
}
- assert(renderer != NULL);
+ // If asked for nothing, don't return anything either :P
+ if(length == 0) {
+ return 0;
+ }
// If paused or stopped, do nothing
if(player->state == KIT_PAUSED) {
@@ -1268,57 +213,15 @@ int Kit_GetSubtitleData(Kit_Player *player, SDL_Renderer *renderer) {
return 0;
}
- unsigned int it;
- Kit_SubtitlePacket *packet = NULL;
-
- // Current sync timestamp
- double cur_subtitle_ts = _GetSystemTime() - player->clock_sync;
-
- // Read a packet from buffer, if one exists. Stop here if not.
- if(SDL_LockMutex(player->smutex) == 0) {
- // Check if refresh is required and remove old subtitles
- it = 0;
- while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(packet->pts_end >= 0 && packet->pts_end < cur_subtitle_ts) {
- Kit_RemoveFromList((Kit_List*)player->sbuffer, it);
- }
- }
-
- // Render subtitle bitmaps
- it = 0;
- while((packet = Kit_IterateList((Kit_List*)player->sbuffer, &it)) != NULL) {
- if(packet->texture == NULL) {
- packet->texture = SDL_CreateTextureFromSurface(renderer, packet->surface);
- SDL_SetTextureBlendMode(packet->texture, SDL_BLENDMODE_BLEND);
- }
- SDL_RenderCopy(renderer, packet->texture, NULL, packet->rect);
- }
-
- // Unlock subtitle buffer mutex.
- SDL_UnlockMutex(player->smutex);
- } else {
- Kit_SetError("Unable to lock subtitle buffer mutex");
- return 0;
- }
-
- return 0;
+ return Kit_GetAudioDecoderData(player->audio_dec, buffer, length);
}
-int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int cur_buf_len) {
+int Kit_GetSubtitleDataTexture(Kit_Player *player, SDL_Texture *texture) {
assert(player != NULL);
-
- // If there is no audio stream, don't bother.
- if(player->src->astream_idx == -1) {
- return 0;
- }
-
- // If asked for nothing, don't return anything either :P
- if(length == 0) {
+ if(player->subtitle_dec == NULL) {
return 0;
}
- assert(buffer != NULL);
-
// If paused or stopped, do nothing
if(player->state == KIT_PAUSED) {
return 0;
@@ -1327,85 +230,19 @@ int Kit_GetAudioData(Kit_Player *player, unsigned char *buffer, int length, int
return 0;
}
- // Read a packet from buffer, if one exists. Stop here if not.
- int ret = 0;
- Kit_AudioPacket *packet = NULL;
- Kit_AudioPacket *n_packet = NULL;
- if(SDL_LockMutex(player->amutex) == 0) {
- packet = (Kit_AudioPacket*)Kit_PeekBuffer((Kit_Buffer*)player->abuffer);
- if(packet == NULL) {
- SDL_UnlockMutex(player->amutex);
- return 0;
- }
-
- int bytes_per_sample = player->aformat.bytes * player->aformat.channels;
- double bps = bytes_per_sample * player->aformat.samplerate;
- double cur_audio_ts = _GetSystemTime() - player->clock_sync + ((double)cur_buf_len / bps);
- double diff = cur_audio_ts - packet->pts;
- int diff_samples = fabs(diff) * player->aformat.samplerate;
-
- if(packet->pts > cur_audio_ts + AUDIO_SYNC_THRESHOLD) {
- // Audio is ahead, fill buffer with some silence
- int max_diff_samples = length / bytes_per_sample;
- int max_samples = (max_diff_samples < diff_samples) ? max_diff_samples : diff_samples;
-
- av_samples_set_silence(
- &buffer,
- 0, // Offset
- max_samples,
- player->aformat.channels,
- _FindAVSampleFormat(player->aformat.format));
-
- int diff_bytes = max_samples * bytes_per_sample;
-
- SDL_UnlockMutex(player->amutex);
- return diff_bytes;
-
- } else if(packet->pts < cur_audio_ts - AUDIO_SYNC_THRESHOLD) {
- // Audio is lagging, skip until good pts is found
-
- while(1) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->abuffer);
- n_packet = (Kit_AudioPacket*)Kit_PeekBuffer((Kit_Buffer*)player->abuffer);
- if(n_packet != NULL) {
- packet = n_packet;
- } else {
- break;
- }
- if(packet->pts > cur_audio_ts - AUDIO_SYNC_THRESHOLD) {
- break;
- }
- }
- }
-
- if(length > 0) {
- ret = Kit_ReadRingBuffer(packet->rb, (char*)buffer, length);
- }
-
- if(Kit_GetRingBufferLength(packet->rb) == 0) {
- Kit_AdvanceBuffer((Kit_Buffer*)player->abuffer);
- _FreeAudioPacket(packet);
- } else {
- double adjust = (double)ret / bps;
- packet->pts += adjust;
- }
-
- SDL_UnlockMutex(player->amutex);
- } else {
- Kit_SetError("Unable to lock audio buffer mutex");
- return 0;
- }
-
- return ret;
+ return Kit_GetSubtitleDecoderDataTexture(player->subtitle_dec, texture);
}
void Kit_GetPlayerInfo(const Kit_Player *player, Kit_PlayerInfo *info) {
assert(player != NULL);
assert(info != NULL);
- AVCodecContext *acodec_ctx = (AVCodecContext*)player->acodec_ctx;
- AVCodecContext *vcodec_ctx = (AVCodecContext*)player->vcodec_ctx;
- AVCodecContext *scodec_ctx = (AVCodecContext*)player->scodec_ctx;
+ Kit_Decoder *audio_dec = player->audio_dec;
+ Kit_Decoder *video_dec = player->video_dec;
+ Kit_Decoder *subtitle_dec = player->subtitle_dec;
+ AVCodecContext *acodec_ctx = audio_dec ? audio_dec->codec_ctx : NULL;
+ AVCodecContext *vcodec_ctx = video_dec ? video_dec->codec_ctx : NULL;
+ AVCodecContext *scodec_ctx = subtitle_dec ? subtitle_dec->codec_ctx : NULL;
// Reset everything to 0. We might not fill all fields.
memset(info, 0, sizeof(Kit_PlayerInfo));
@@ -1427,58 +264,99 @@ void Kit_GetPlayerInfo(const Kit_Player *player, Kit_PlayerInfo *info) {
}
}
+static void _SetClockSync(Kit_Player *player) {
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ double sync = _GetSystemTime();
+ Kit_SetDecoderClockSync(player->video_dec, sync);
+ Kit_SetDecoderClockSync(player->audio_dec, sync);
+ Kit_SetDecoderClockSync(player->subtitle_dec, sync);
+ SDL_UnlockMutex(player->dec_lock);
+ }
+}
+
+static void _ChangeClockSync(Kit_Player *player, double delta) {
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ Kit_ChangeDecoderClockSync(player->video_dec, delta);
+ Kit_ChangeDecoderClockSync(player->audio_dec, delta);
+ Kit_ChangeDecoderClockSync(player->subtitle_dec, delta);
+ SDL_UnlockMutex(player->dec_lock);
+ }
+}
+
Kit_PlayerState Kit_GetPlayerState(const Kit_Player *player) {
assert(player != NULL);
-
return player->state;
}
void Kit_PlayerPlay(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state == KIT_PLAYING) {
- return;
- }
- if(player->state == KIT_STOPPED) {
- player->clock_sync = _GetSystemTime();
- }
- if(player->state == KIT_PAUSED) {
- player->clock_sync += _GetSystemTime() - player->pause_start;
+ double tmp;
+ switch(player->state) {
+ case KIT_PLAYING:
+ case KIT_CLOSED:
+ break;
+ case KIT_PAUSED:
+ tmp = _GetSystemTime() - player->pause_started;
+ _ChangeClockSync(player, tmp);
+ player->state = KIT_PLAYING;
+ break;
+ case KIT_STOPPED:
+ _SetClockSync(player);
+ player->state = KIT_PLAYING;
+ break;
}
- player->state = KIT_PLAYING;
}
void Kit_PlayerStop(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state == KIT_STOPPED) {
- return;
+ switch(player->state) {
+ case KIT_STOPPED:
+ case KIT_CLOSED:
+ break;
+ case KIT_PLAYING:
+ case KIT_PAUSED:
+ player->state = KIT_STOPPED;
+ break;
}
- player->state = KIT_STOPPED;
}
void Kit_PlayerPause(Kit_Player *player) {
assert(player != NULL);
-
- if(player->state != KIT_PLAYING) {
- return;
- }
- player->pause_start = _GetSystemTime();
player->state = KIT_PAUSED;
+ player->pause_started = _GetSystemTime();
}
-int Kit_PlayerSeek(Kit_Player *player, double m_time) {
+int Kit_PlayerSeek(Kit_Player *player, double seek_set) {
assert(player != NULL);
+ double position, duration;
+ int64_t seek_target;
- // Send packets to control stream
- if(SDL_LockMutex(player->cmutex) == 0) {
- // Flush audio and video buffers, then set seek, then unlock control queue mutex.
- Kit_WriteBuffer((Kit_Buffer*)player->cbuffer, _CreateControlPacket(KIT_CONTROL_FLUSH, 0));
- Kit_WriteBuffer((Kit_Buffer*)player->cbuffer, _CreateControlPacket(KIT_CONTROL_SEEK, m_time));
- SDL_UnlockMutex(player->cmutex);
- } else {
- Kit_SetError("Unable to lock control queue mutex");
- return 1;
+ if(SDL_LockMutex(player->dec_lock) == 0) {
+ duration = Kit_GetPlayerDuration(player);
+ position = Kit_GetPlayerPosition(player);
+ if(seek_set <= 0) {
+ seek_set = 0;
+ }
+ if(seek_set >= duration) {
+ seek_set = duration;
+ }
+
+ // Set source to timestamp
+ AVFormatContext *format_ctx = player->src->format_ctx;
+ seek_target = seek_set * AV_TIME_BASE;
+ if(avformat_seek_file(format_ctx, -1, seek_target, seek_target, seek_target, AVSEEK_FLAG_ANY) < 0) {
+ Kit_SetError("Unable to seek source");
+ SDL_UnlockMutex(player->dec_lock);
+ return 1;
+ } else {
+ _ChangeClockSync(player, position - seek_set);
+ Kit_ClearDecoderBuffers(player->video_dec);
+ Kit_ClearDecoderBuffers(player->audio_dec);
+ Kit_ClearDecoderBuffers(player->subtitle_dec);
+ }
+
+ // That's it. Unlock and continue.
+ SDL_UnlockMutex(player->dec_lock);
}
return 0;
@@ -1487,12 +365,18 @@ int Kit_PlayerSeek(Kit_Player *player, double m_time) {
double Kit_GetPlayerDuration(const Kit_Player *player) {
assert(player != NULL);
- AVFormatContext *fmt_ctx = (AVFormatContext *)player->src->format_ctx;
+ AVFormatContext *fmt_ctx = player->src->format_ctx;
return (fmt_ctx->duration / AV_TIME_BASE);
}
double Kit_GetPlayerPosition(const Kit_Player *player) {
assert(player != NULL);
- return player->vclock_pos;
+ if(player->video_dec) {
+ return ((Kit_Decoder*)player->video_dec)->clock_pos;
+ }
+ if(player->audio_dec) {
+ return ((Kit_Decoder*)player->audio_dec)->clock_pos;
+ }
+ return 0;
}
diff --git a/src/kitsource.c b/src/kitsource.c
index 075bcbd..9e47dcc 100644
--- a/src/kitsource.c
+++ b/src/kitsource.c
@@ -1,13 +1,13 @@
-#include "kitchensink/kitsource.h"
-#include "kitchensink/kiterror.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
+#include "kitchensink/kitsource.h"
+#include "kitchensink/kiterror.h"
Kit_Source* Kit_CreateSourceFromUrl(const char *url) {
assert(url != NULL);
@@ -34,9 +34,9 @@ Kit_Source* Kit_CreateSourceFromUrl(const char *url) {
}
// Find best streams for defaults
- src->astream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO);
- src->vstream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO);
- src->sstream_idx = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE);
+ src->audio_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_AUDIO);
+ src->video_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_VIDEO);
+ src->subtitle_stream_index = Kit_GetBestSourceStream(src, KIT_STREAMTYPE_SUBTITLE);
return src;
exit_1:
@@ -102,9 +102,9 @@ int Kit_GetBestSourceStream(const Kit_Source *src, const Kit_StreamType type) {
int Kit_SetSourceStream(Kit_Source *src, const Kit_StreamType type, int index) {
assert(src != NULL);
switch(type) {
- case KIT_STREAMTYPE_AUDIO: src->astream_idx = index; break;
- case KIT_STREAMTYPE_VIDEO: src->vstream_idx = index; break;
- case KIT_STREAMTYPE_SUBTITLE: src->sstream_idx = index; break;
+ case KIT_STREAMTYPE_AUDIO: src->audio_stream_index = index; break;
+ case KIT_STREAMTYPE_VIDEO: src->video_stream_index = index; break;
+ case KIT_STREAMTYPE_SUBTITLE: src->subtitle_stream_index = index; break;
default:
Kit_SetError("Invalid stream type");
return 1;
@@ -115,9 +115,9 @@ int Kit_SetSourceStream(Kit_Source *src, const Kit_StreamType type, int index) {
int Kit_GetSourceStream(const Kit_Source *src, const Kit_StreamType type) {
assert(src != NULL);
switch(type) {
- case KIT_STREAMTYPE_AUDIO: return src->astream_idx;
- case KIT_STREAMTYPE_VIDEO: return src->vstream_idx;
- case KIT_STREAMTYPE_SUBTITLE: return src->sstream_idx;
+ case KIT_STREAMTYPE_AUDIO: return src->audio_stream_index;
+ case KIT_STREAMTYPE_VIDEO: return src->video_stream_index;
+ case KIT_STREAMTYPE_SUBTITLE: return src->subtitle_stream_index;
default:
break;
}
diff --git a/src/kitutils.c b/src/kitutils.c
index b618c07..1c62ddc 100644
--- a/src/kitutils.c
+++ b/src/kitutils.c
@@ -1,8 +1,8 @@
+#include <SDL2/SDL.h>
+
#include "kitchensink/kitutils.h"
#include "kitchensink/kitsource.h"
-#include <SDL2/SDL.h>
-
const char* Kit_GetSDLAudioFormatString(unsigned int type) {
switch(type) {
case AUDIO_S8: return "AUDIO_S8";