diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index be6671002..92baa6b1e 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -5,6 +5,7 @@ endif() add_subdirectory(glad) add_subdirectory(ipc-util) +add_subdirectory(libff) find_package(Jansson 2.5 QUIET) diff --git a/deps/libff/CMakeLists.txt b/deps/libff/CMakeLists.txt new file mode 100644 index 000000000..d6f6cb1a1 --- /dev/null +++ b/deps/libff/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required (VERSION 2.8.11) +project (libff) + +find_package(FFMpeg REQUIRED + COMPONENTS avcodec avfilter avdevice avutil swscale avformat swresample) + +include_directories(${FFMPEG_INCLUDE_DIRS}) + +if(WIN32) + include_directories(../w32-pthreads) + add_definitions(-Dinline=__inline) +endif(WIN32) + +set(libff_HEADERS + libff/ff-callbacks.h + libff/ff-circular-queue.h + libff/ff-clock.h + libff/ff-frame.h + libff/ff-packet-queue.h + libff/ff-timer.h + # + libff/ff-demuxer.h + # + libff/ff-decoder.h) + +set(libff_SOURCES + libff/ff-callbacks.c + libff/ff-circular-queue.c + libff/ff-clock.c + libff/ff-packet-queue.c + libff/ff-timer.c + # + libff/ff-demuxer.c + # + libff/ff-decoder.c + libff/ff-audio-decoder.c + libff/ff-video-decoder.c) + +add_library (libff STATIC + ${libff_HEADERS} + ${libff_SOURCES}) + +target_include_directories(libff + PUBLIC .) + +if(NOT MSVC) + if(NOT MINGW) + target_compile_options(libff PRIVATE -fPIC) + endif() +endif() + +target_link_libraries (libff) diff --git a/deps/libff/libff/ff-audio-decoder.c b/deps/libff/libff/ff-audio-decoder.c new file mode 100644 index 000000000..b3ebe28e1 --- /dev/null +++ b/deps/libff/libff/ff-audio-decoder.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-callbacks.h" +#include "ff-circular-queue.h" +#include "ff-clock.h" +#include "ff-decoder.h" +#include "ff-frame.h" +#include "ff-packet-queue.h" +#include "ff-timer.h" + +#include +#include +#include + +#include + +static inline void shrink_packet(AVPacket *packet, int packet_length) +{ + if (packet_length <= packet->size) { + int remaining = packet->size - packet_length; + + memmove(packet->data, &packet->data[packet_length], remaining); + av_shrink_packet(packet, remaining); + } +} + +static int decode_frame(struct ff_decoder *decoder, + AVPacket *packet, AVFrame *frame, bool *frame_complete) +{ + int packet_length; + int ret; + + while (true) { + while (packet->size > 0) { + int complete; + + packet_length = avcodec_decode_audio4(decoder->codec, + frame, &complete, + packet); + + if (packet_length < 0) + break; + + shrink_packet(packet, packet_length); + + if (complete == 0 || frame->nb_samples <= 0) + continue; + + *frame_complete = complete != 0; + + return frame->nb_samples * + av_get_bytes_per_sample(frame->format); + } + + if (packet->data != NULL) + av_packet_unref(packet); + + ret = packet_queue_get(&decoder->packet_queue, packet, 1); + if (ret == FF_PACKET_FAIL) { + return -1; + } + + if (packet->data == decoder->packet_queue.flush_packet.data) { + avcodec_flush_buffers(decoder->codec); + + // we were flushed, so try to get another packet + ret = packet_queue_get(&decoder->packet_queue, + packet, 1); + if (ret == FF_PACKET_FAIL) { + return -1; + } + } + } +} + +static bool queue_frame(struct ff_decoder *decoder, AVFrame *frame, + double best_effort_pts) +{ + struct ff_frame *queue_frame; + bool call_initialize; + + ff_circular_queue_wait_write(&decoder->frame_queue); + + if (decoder->abort) { + return false; + } + + queue_frame = ff_circular_queue_peek_write(&decoder->frame_queue); + + AVCodecContext *codec = decoder->codec; + call_initialize = (queue_frame->frame == NULL + || queue_frame->frame->channels != codec->channels + || queue_frame->frame->sample_rate != codec->sample_rate + || queue_frame->frame->format != codec->sample_fmt); + + if (queue_frame->frame != NULL) + av_frame_free(&queue_frame->frame); + + queue_frame->frame = av_frame_clone(frame); + + if (call_initialize) + ff_callbacks_frame_initialize(queue_frame, decoder->callbacks); + + queue_frame->pts = best_effort_pts; + + ff_circular_queue_advance_write(&decoder->frame_queue, queue_frame); + + return true; +} + +void *ff_audio_decoder_thread(void *opaque_audio_decoder) +{ + struct ff_decoder *decoder = opaque_audio_decoder; + + AVPacket packet = {0}; + bool frame_complete; + AVFrame *frame = av_frame_alloc(); + + while (!decoder->abort) { + if (decode_frame(decoder, &packet, frame, &frame_complete) < 0) { + av_free_packet(&packet); + continue; + } + + // Did we get a audio frame? + if (frame_complete) { + // If we don't have a good PTS, try to guess based + // on last received PTS provided plus prediction + // This function returns a pts scaled to stream + // time base + double best_effort_pts = + ff_decoder_get_best_effort_pts(decoder, frame); + queue_frame(decoder, frame, best_effort_pts); + av_frame_unref(frame); + } + + av_free_packet(&packet); + } + + av_frame_free(&frame); + return NULL; +} diff --git a/deps/libff/libff/ff-callbacks.c b/deps/libff/libff/ff-callbacks.c new file mode 100644 index 000000000..ac2d3b90a --- /dev/null +++ b/deps/libff/libff/ff-callbacks.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-callbacks.h" + +bool ff_callbacks_frame(struct ff_callbacks *callbacks, + struct ff_frame *frame) +{ + if (callbacks->frame == NULL) + return true; + + return callbacks->frame(frame, callbacks->opaque); +} + +bool ff_callbacks_format(struct ff_callbacks *callbacks, + AVCodecContext *codec_context) +{ + if (callbacks->format == NULL) + return true; + + return callbacks->format(codec_context, callbacks->opaque); +} + +bool ff_callbacks_initialize(struct ff_callbacks *callbacks) +{ + if (callbacks->initialize == NULL) + return true; + + return callbacks->initialize(callbacks->opaque); +} + +bool ff_callbacks_frame_initialize(struct ff_frame *frame, + struct ff_callbacks *callbacks) +{ + if (callbacks->frame_initialize == NULL) + return true; + + return callbacks->frame_initialize(frame, callbacks->opaque); +} + +bool ff_callbacks_frame_free(struct ff_frame *frame, + struct ff_callbacks *callbacks) +{ + if (callbacks->frame_free == NULL) + return true; + + return callbacks->frame_free(frame, callbacks->opaque); +} diff --git a/deps/libff/libff/ff-callbacks.h b/deps/libff/libff/ff-callbacks.h new file mode 100644 index 000000000..5e545d708 --- /dev/null +++ b/deps/libff/libff/ff-callbacks.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "ff-frame.h" + +#include +#include + +typedef bool (*ff_callback_frame)(struct ff_frame *frame, void *opaque); +typedef bool (*ff_callback_format)(AVCodecContext *codec_context, void *opaque); +typedef bool (*ff_callback_initialize)(void *opaque); + + +struct ff_callbacks { + ff_callback_frame frame; + ff_callback_format format; + ff_callback_initialize initialize; + ff_callback_frame frame_initialize; + ff_callback_frame frame_free; + void *opaque; +}; + +bool ff_callbacks_frame(struct ff_callbacks *callbacks, + struct ff_frame *frame); +bool ff_callbacks_format(struct ff_callbacks *callbacks, + AVCodecContext *codec_context); +bool ff_callbacks_initialize(struct ff_callbacks *callbacks); +bool ff_callbacks_frame_initialize(struct ff_frame *frame, + struct ff_callbacks *callbacks); +bool ff_callbacks_frame_free(struct ff_frame *frame, + struct ff_callbacks *callbacks); diff --git a/deps/libff/libff/ff-circular-queue.c b/deps/libff/libff/ff-circular-queue.c new file mode 100644 index 000000000..027f95831 --- /dev/null +++ b/deps/libff/libff/ff-circular-queue.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-circular-queue.h" + +static void *queue_fetch_or_alloc(struct ff_circular_queue *cq, + int index) +{ + if (cq->slots[index] == NULL) + cq->slots[index] = av_mallocz(cq->item_size); + + return cq->slots[index]; +} + +static void queue_lock(struct ff_circular_queue *cq) +{ + pthread_mutex_lock(&cq->mutex); +} + +static void queue_unlock(struct ff_circular_queue *cq) +{ + pthread_mutex_unlock(&cq->mutex); +} + +static void queue_signal(struct ff_circular_queue *cq) +{ + pthread_cond_signal(&cq->cond); +} + +static void queue_wait(struct ff_circular_queue *cq) +{ + pthread_cond_wait(&cq->cond, &cq->mutex); +} + +bool ff_circular_queue_init(struct ff_circular_queue *cq, int item_size, + int capacity) +{ + memset(cq, 0, sizeof(struct ff_circular_queue)); + + cq->item_size = item_size; + cq->capacity = capacity; + cq->abort = false; + + cq->slots = av_mallocz(capacity * sizeof(void *)); + + if (cq->slots == NULL) + goto fail; + + cq->size = 0; + cq->write_index = 0; + cq->read_index = 0; + + if (pthread_mutex_init(&cq->mutex, NULL) != 0) + goto fail1; + + if (pthread_cond_init(&cq->cond, NULL) != 0) + goto fail2; + + return true; + +fail2: + pthread_mutex_destroy(&cq->mutex); +fail1: + av_free(cq->slots); +fail: + return false; +} + +void ff_circular_queue_abort(struct ff_circular_queue *cq) +{ + queue_lock(cq); + cq->abort = true; + queue_signal(cq); + queue_unlock(cq); +} + +void ff_circular_queue_free(struct ff_circular_queue *cq) +{ + ff_circular_queue_abort(cq); + + if (cq->slots != NULL) + av_free(cq->slots); + + pthread_mutex_destroy(&cq->mutex); + pthread_cond_destroy(&cq->cond); +} + +void ff_circular_queue_wait_write(struct ff_circular_queue *cq) +{ + queue_lock(cq); + + while (cq->size >= cq->capacity && !cq->abort) + queue_wait(cq); + + queue_unlock(cq); +} + +void *ff_circular_queue_peek_write(struct ff_circular_queue *cq) +{ + return queue_fetch_or_alloc(cq, cq->write_index); +} + +void ff_circular_queue_advance_write(struct ff_circular_queue *cq, void *item) +{ + cq->slots[cq->write_index] = item; + cq->write_index = ++cq->write_index % cq->capacity; + + queue_lock(cq); + ++cq->size; + queue_unlock(cq); +} + +void *ff_circular_queue_peek_read(struct ff_circular_queue *cq) +{ + return queue_fetch_or_alloc(cq, cq->read_index); +} + +void ff_circular_queue_advance_read(struct ff_circular_queue *cq) +{ + cq->read_index = ++cq->read_index % cq->capacity; + queue_lock(cq); + --cq->size; + queue_signal(cq); + queue_unlock(cq); +} + + diff --git a/deps/libff/libff/ff-circular-queue.h b/deps/libff/libff/ff-circular-queue.h new file mode 100644 index 000000000..3d6da7532 --- /dev/null +++ b/deps/libff/libff/ff-circular-queue.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +struct ff_circular_queue { + pthread_mutex_t mutex; + pthread_cond_t cond; + + void **slots; + + int item_size; + int capacity; + int size; + + int write_index; + int read_index; + + bool abort; +}; + +typedef struct ff_circular_queue ff_circular_queue_t; + +bool ff_circular_queue_init(struct ff_circular_queue *cq, int item_size, + int capacity); +void ff_circular_queue_abort(struct ff_circular_queue *cq); +void ff_circular_queue_free(struct ff_circular_queue *cq); + +void ff_circular_queue_wait_write(struct ff_circular_queue *cq); +void *ff_circular_queue_peek_write(struct ff_circular_queue *cq); +void ff_circular_queue_advance_write(struct ff_circular_queue *cq, void *item); +void *ff_circular_queue_peek_read(struct ff_circular_queue *cq); +void ff_circular_queue_advance_read(struct ff_circular_queue *cq); diff --git a/deps/libff/libff/ff-clock.c b/deps/libff/libff/ff-clock.c new file mode 100644 index 000000000..6939d47b7 --- /dev/null +++ b/deps/libff/libff/ff-clock.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-clock.h" + +double ff_get_sync_clock(struct ff_clock *clock) +{ + return clock->sync_clock(clock->opaque); +} diff --git a/deps/libff/libff/ff-clock.h b/deps/libff/libff/ff-clock.h new file mode 100644 index 000000000..5f8d4f49d --- /dev/null +++ b/deps/libff/libff/ff-clock.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#define AV_SYNC_THRESHOLD 0.01 +#define AV_NOSYNC_THRESHOLD 10.0 + +enum ff_av_sync_type { + AV_SYNC_AUDIO_MASTER, + AV_SYNC_VIDEO_MASTER, + AV_SYNC_EXTERNAL_MASTER, +}; + +typedef double (*ff_sync_clock)(void *opaque); + +struct ff_clock { + ff_sync_clock sync_clock; + enum ff_av_sync_type sync_type; + void *opaque; +}; + +typedef struct ff_clock ff_clock_t; + +double ff_get_sync_clock(struct ff_clock *clock); diff --git a/deps/libff/libff/ff-decoder.c b/deps/libff/libff/ff-decoder.c new file mode 100644 index 000000000..2d1ecf7f7 --- /dev/null +++ b/deps/libff/libff/ff-decoder.c @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-decoder.h" + +#include +#include + +typedef void *(*ff_decoder_thread_t)(void *opaque_decoder); + +extern void *ff_audio_decoder_thread(void *opaque_audio_decoder); +extern void *ff_video_decoder_thread(void *opaque_video_decoder); + +struct ff_decoder *ff_decoder_init(AVCodecContext *codec_context, + AVStream *stream, unsigned int packet_queue_size, + unsigned int frame_queue_size) +{ + bool success; + + assert(codec_context != NULL); + assert(stream != NULL); + + struct ff_decoder *decoder = av_mallocz(sizeof(struct ff_decoder)); + + if (decoder == NULL) + goto fail; + + decoder->codec = codec_context; + decoder->codec->opaque = decoder; + decoder->stream = stream; + decoder->abort = false; + + decoder->packet_queue_size = packet_queue_size; + if (!packet_queue_init(&decoder->packet_queue)) + goto fail1; + + decoder->timer_next_wake = (double)av_gettime() / 1000000.0; + decoder->previous_pts_diff = 40e-3; + decoder->current_pts_time = av_gettime(); + decoder->predicted_pts = 0; + + success = ff_timer_init(&decoder->refresh_timer, ff_decoder_refresh, + decoder); + if (!success) + goto fail2; + + success = ff_circular_queue_init(&decoder->frame_queue, + sizeof(struct ff_frame), frame_queue_size); + if (!success) + goto fail3; + + return decoder; + +fail3: + ff_timer_free(&decoder->refresh_timer); +fail2: + packet_queue_free(&decoder->packet_queue); +fail1: + av_free(decoder); +fail: + return NULL; +} + +bool ff_decoder_start(struct ff_decoder *decoder) +{ + assert(decoder != NULL); + + ff_decoder_thread_t decoder_thread; + if (decoder->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + decoder_thread = ff_audio_decoder_thread; + } else if (decoder->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + decoder_thread = ff_video_decoder_thread; + } else { + av_log(NULL, AV_LOG_ERROR, "no decoder found for type %d", + decoder->codec->codec_type); + return false; + } + + ff_decoder_schedule_refresh(decoder, 40); + + return (pthread_create(&decoder->decoder_thread, NULL, + decoder_thread, decoder) != 0); +} + +void ff_decoder_free(struct ff_decoder *decoder) +{ + void *decoder_thread_result; + int i; + + assert(decoder != NULL); + + decoder->abort = true; + + ff_circular_queue_abort(&decoder->frame_queue); + packet_queue_abort(&decoder->packet_queue); + + ff_timer_free(&decoder->refresh_timer); + + pthread_join(decoder->decoder_thread, &decoder_thread_result); + + for (i = 0; i < decoder->frame_queue.capacity; i++) { + void *item = decoder->frame_queue.slots[i]; + struct ff_frame *frame = (struct ff_frame *)item; + + ff_callbacks_frame_free(frame, decoder->callbacks); + + if (frame != NULL && frame->frame != NULL) + av_frame_unref(frame->frame); + if (frame != NULL) + av_free(frame); + } + + packet_queue_free(&decoder->packet_queue); + ff_circular_queue_free(&decoder->frame_queue); + + avcodec_close(decoder->codec); + + av_free(decoder); +} + +void ff_decoder_schedule_refresh(struct ff_decoder *decoder, int delay) +{ + ff_timer_schedule(&decoder->refresh_timer, 1000*delay); +} + +double ff_decoder_clock(void *opaque) +{ + struct ff_decoder *decoder = opaque; + double delta = (av_gettime() - decoder->current_pts_time) / 1000000.0; + return decoder->current_pts + delta; +} + +static double get_sync_adjusted_pts_diff(struct ff_decoder *decoder, + double pts, double pts_diff) +{ + double new_pts_diff = pts_diff; + double sync_time = ff_get_sync_clock(decoder->clock); + double diff = pts - sync_time; + double sync_threshold; + + sync_threshold = (pts_diff > AV_SYNC_THRESHOLD) + ? pts_diff : AV_SYNC_THRESHOLD; + + if (fabs(diff) < AV_NOSYNC_THRESHOLD) { + if (diff <= -sync_threshold) { + new_pts_diff = 0; + + } else if (diff >= sync_threshold) { + new_pts_diff = 2 * pts_diff; + } + } + + return new_pts_diff; +} + +void ff_decoder_refresh(void *opaque) +{ + struct ff_decoder *decoder = (struct ff_decoder *)opaque; + + struct ff_frame *frame; + + if (decoder && decoder->stream) { + if (decoder->frame_queue.size == 0) { + if (!decoder->eof) { + // We expected a frame, but there were none available + // Schedule another call as soon as possible + ff_decoder_schedule_refresh(decoder, 1); + } else { + decoder->refresh_timer.abort = true; + // no more refreshes, we are at the eof + av_log(NULL, AV_LOG_INFO, + "refresh timer stopping; eof"); + return; + } + } else { + double pts_diff; + double delay_until_next_wake; + frame = ff_circular_queue_peek_read( + &decoder->frame_queue); + + decoder->current_pts = frame->pts; + decoder->current_pts_time = av_gettime(); + + // the amount of time until we need to display this + // frame + pts_diff = frame->pts - decoder->previous_pts; + + if (pts_diff <= 0 || pts_diff >= 1.0) { + // if diff is invalid, use previous + pts_diff = decoder->previous_pts_diff; + } + + // save for next time + decoder->previous_pts_diff = pts_diff; + decoder->previous_pts = frame->pts; + + // if not synced against natural clock + if (decoder->clock->sync_type + != decoder->natural_sync_clock) { + pts_diff = get_sync_adjusted_pts_diff(decoder, + frame->pts, pts_diff); + } + + decoder->timer_next_wake += pts_diff; + + // compute the amount of time until next refresh + delay_until_next_wake = decoder->timer_next_wake - + (av_gettime() / 1000000.0L); + if (delay_until_next_wake < 0.010L) { + // accellerate next wake up + delay_until_next_wake = 0.010L; + } + + ff_callbacks_frame(decoder->callbacks, frame); + + ff_decoder_schedule_refresh(decoder, + (int)(delay_until_next_wake * 1000 + + 0.5L)); + + ff_circular_queue_advance_read(&decoder->frame_queue); + } + } else { + ff_decoder_schedule_refresh(decoder, 100); + } +} + +bool ff_decoder_full(struct ff_decoder *decoder) +{ + if (decoder == NULL) + return false; + + return (decoder->packet_queue.total_size > decoder->packet_queue_size); +} + +bool ff_decoder_accept(struct ff_decoder *decoder, AVPacket *packet) +{ + if (decoder && packet->stream_index == decoder->stream->index) { + packet_queue_put(&decoder->packet_queue, packet); + return true; + } + + return false; +} + +double ff_decoder_get_best_effort_pts(struct ff_decoder *decoder, + AVFrame *frame) +{ + // this is how long each frame is added to the amount of repeated frames + // according to the codec + double estimated_frame_delay; + int64_t best_effort_pts; + double d_pts; + + // This function is ffmpeg only, replace with frame->pkt_pts + // if you are trying to compile for libav as a temporary + // measure + best_effort_pts = av_frame_get_best_effort_timestamp(frame); + + if (best_effort_pts != AV_NOPTS_VALUE) { + // Since the best effort pts came from the stream we use his + // time base + d_pts = best_effort_pts * av_q2d(decoder->stream->time_base); + decoder->predicted_pts = d_pts; + } else { + d_pts = decoder->predicted_pts; + } + + // Update our predicted pts to include the repeated picture count + // Our predicted pts clock is based on the codecs time base + estimated_frame_delay = av_frame_get_pkt_duration(frame) + * av_q2d(decoder->codec->time_base); + // Add repeat frame delay + estimated_frame_delay += frame->repeat_pict + / (1.0L / estimated_frame_delay); + + decoder->predicted_pts += estimated_frame_delay; + + return d_pts; +} diff --git a/deps/libff/libff/ff-decoder.h b/deps/libff/libff/ff-decoder.h new file mode 100644 index 000000000..8e2c882ff --- /dev/null +++ b/deps/libff/libff/ff-decoder.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "ff-callbacks.h" +#include "ff-circular-queue.h" +#include "ff-clock.h" +#include "ff-packet-queue.h" +#include "ff-timer.h" + +#include + +struct ff_decoder { + AVCodecContext *codec; + AVStream *stream; + + pthread_t decoder_thread; + struct ff_timer refresh_timer; + + struct ff_callbacks *callbacks; + struct ff_packet_queue packet_queue; + struct ff_circular_queue frame_queue; + unsigned int packet_queue_size; + + double timer_next_wake; + double previous_pts; // previous decoded frame's pts + double previous_pts_diff; // previous decoded frame pts delay + double predicted_pts; // predicted pts of next frame + double current_pts; // pts of the most recently dispatched frame + int64_t current_pts_time; // clock time when current_pts was set + + struct ff_clock *clock; + enum ff_av_sync_type natural_sync_clock; + + bool eof; + bool abort; +}; + +typedef struct ff_decoder ff_decoder_t; + +struct ff_decoder *ff_decoder_init(AVCodecContext *codec_context, + AVStream *stream, unsigned int packet_queue_size, + unsigned int frame_queue_size); +bool ff_decoder_start(struct ff_decoder *decoder); +void ff_decoder_free(struct ff_decoder *decoder); + +bool ff_decoder_full(struct ff_decoder *decoder); +bool ff_decoder_accept(struct ff_decoder *decoder, AVPacket *packet); + +double ff_decoder_clock(void *opaque); + +void ff_decoder_schedule_refresh(struct ff_decoder *decoder, int delay); +void ff_decoder_refresh(void *opaque); + +double ff_decoder_get_best_effort_pts(struct ff_decoder *decoder, + AVFrame *frame); diff --git a/deps/libff/libff/ff-demuxer.c b/deps/libff/libff/ff-demuxer.c new file mode 100644 index 000000000..2b5c03288 --- /dev/null +++ b/deps/libff/libff/ff-demuxer.c @@ -0,0 +1,522 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-demuxer.h" + +#include +#include +#include +#include + +#include + +#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER + +#define AUDIO_FRAME_QUEUE_SIZE 1 +#define VIDEO_FRAME_QUEUE_SIZE 1 + +#define AUDIO_PACKET_QUEUE_SIZE (5 * 16 * 1024) +#define VIDEO_PACKET_QUEUE_SIZE (5 * 256 * 1024) + +static void *demux_thread(void *opaque_demuxer); + +struct ff_demuxer *ff_demuxer_init() +{ + struct ff_demuxer *demuxer; + + av_register_all(); + avdevice_register_all(); + avfilter_register_all(); + + demuxer = av_mallocz(sizeof(struct ff_demuxer)); + if (demuxer == NULL) + return NULL; + + demuxer->clock.sync_type = DEFAULT_AV_SYNC_TYPE; + demuxer->options.audio_frame_queue_size = AUDIO_FRAME_QUEUE_SIZE; + demuxer->options.video_frame_queue_size = VIDEO_FRAME_QUEUE_SIZE; + demuxer->options.audio_packet_queue_size = AUDIO_PACKET_QUEUE_SIZE; + demuxer->options.video_packet_queue_size = VIDEO_PACKET_QUEUE_SIZE; + demuxer->options.is_hw_decoding = false; + + return demuxer; +} + +bool ff_demuxer_open(struct ff_demuxer *demuxer, char *input, + char *input_format) +{ + int ret; + + demuxer->input = av_strdup(input); + if (input_format != NULL) + demuxer->input_format = av_strdup(input_format); + + ret = pthread_create(&demuxer->demuxer_thread, NULL, demux_thread, + demuxer); + return ret == 0; +} + +void ff_demuxer_free(struct ff_demuxer *demuxer) +{ + void *demuxer_thread_result; + + demuxer->abort = true; + + pthread_join(demuxer->demuxer_thread, &demuxer_thread_result); + + if (demuxer->input != NULL) + av_free(demuxer->input); + + if (demuxer->input_format != NULL) + av_free(demuxer->input_format); + + if (demuxer->audio_decoder != NULL) + ff_decoder_free(demuxer->audio_decoder); + + if (demuxer->video_decoder != NULL) + ff_decoder_free(demuxer->video_decoder); + + if (demuxer->format_context) + avformat_free_context(demuxer->format_context); + + av_free(demuxer); +} + +void ff_demuxer_set_callbacks(struct ff_callbacks *callbacks, + ff_callback_frame frame, + ff_callback_format format, + ff_callback_initialize initialize, + ff_callback_frame frame_initialize, + ff_callback_frame frame_free, + void *opaque) +{ + callbacks->opaque = opaque; + callbacks->frame = frame; + callbacks->format = format; + callbacks->initialize = initialize; + callbacks->frame_initialize = frame_initialize; + callbacks->frame_free = frame_free; +} + +static int demuxer_interrupted_callback(void *opaque) +{ + return opaque != NULL && ((struct ff_demuxer *)opaque)->abort; +} + +static double ff_external_clock(void *opaque) +{ + (void)opaque; + + return av_gettime() / 1000000.0; +} + +static bool set_clock_sync_type(struct ff_demuxer *demuxer) +{ + if (demuxer->video_decoder == NULL) { + if (demuxer->clock.sync_type == AV_SYNC_VIDEO_MASTER) + demuxer->clock.sync_type = AV_SYNC_AUDIO_MASTER; + } + + if (demuxer->audio_decoder == NULL) { + if (demuxer->clock.sync_type == AV_SYNC_AUDIO_MASTER) + demuxer->clock.sync_type = AV_SYNC_VIDEO_MASTER; + } + + switch (demuxer->clock.sync_type) { + case AV_SYNC_AUDIO_MASTER: + demuxer->clock.sync_clock = ff_decoder_clock; + demuxer->clock.opaque = demuxer->audio_decoder; + break; + case AV_SYNC_VIDEO_MASTER: + demuxer->clock.sync_clock = ff_decoder_clock; + demuxer->clock.opaque = demuxer->video_decoder; + break; + case AV_SYNC_EXTERNAL_MASTER: + demuxer->clock.sync_clock = ff_external_clock; + demuxer->clock.opaque = NULL; + break; + default: + return false; + } + + return true; +} + +AVHWAccel *find_hwaccel_codec(AVCodecContext *codec_context) +{ + AVHWAccel *hwaccel = NULL; + + while ((hwaccel = av_hwaccel_next(hwaccel)) != NULL) { + if (hwaccel->id == codec_context->codec_id && + (hwaccel->pix_fmt == AV_PIX_FMT_VDA_VLD || + hwaccel->pix_fmt == AV_PIX_FMT_DXVA2_VLD || + hwaccel->pix_fmt == AV_PIX_FMT_VAAPI_VLD)) { + return hwaccel; + } + + } + + return NULL; +} + +enum AVPixelFormat get_hwaccel_format(struct AVCodecContext *s, + const enum AVPixelFormat * fmt) +{ + (void)s; + (void)fmt; + + // for now force output to common denominator + return AV_PIX_FMT_YUV420P; +} + +static bool initialize_decoder(struct ff_demuxer *demuxer, + AVCodecContext *codec_context, AVStream *stream) +{ + switch (codec_context->codec_type) { + case AVMEDIA_TYPE_AUDIO: + demuxer->audio_decoder = ff_decoder_init( + codec_context, stream, + demuxer->options.audio_packet_queue_size, + demuxer->options.audio_frame_queue_size); + + demuxer->audio_decoder->natural_sync_clock = + AV_SYNC_AUDIO_MASTER; + demuxer->audio_decoder->clock = &demuxer->clock; + + demuxer->audio_decoder->callbacks = &demuxer->audio_callbacks; + + if (!ff_callbacks_format(&demuxer->audio_callbacks, + codec_context)) { + ff_decoder_free(demuxer->audio_decoder); + demuxer->audio_decoder = NULL; + return false; + } + + demuxer->audio_decoder = demuxer->audio_decoder; + return true; + + case AVMEDIA_TYPE_VIDEO: + demuxer->video_decoder = ff_decoder_init( + codec_context, stream, + demuxer->options.video_packet_queue_size, + demuxer->options.video_frame_queue_size); + + demuxer->video_decoder->natural_sync_clock = + AV_SYNC_VIDEO_MASTER; + demuxer->video_decoder->clock = &demuxer->clock; + + demuxer->video_decoder->callbacks = &demuxer->video_callbacks; + + if (!ff_callbacks_format(&demuxer->video_callbacks, + codec_context)) { + ff_decoder_free(demuxer->video_decoder); + demuxer->video_decoder = NULL; + return false; + } + + return true; + default: + return false; + } +} + +static bool find_decoder(struct ff_demuxer *demuxer, AVStream *stream) +{ + AVCodecContext *codec_context = NULL; + AVCodec *codec = NULL; + AVDictionary *options_dict = NULL; + int ret; + + codec_context = stream->codec; + + // enable reference counted frames since we may have a buffer size + // > 1 + codec_context->refcounted_frames = 1; + + if (demuxer->options.is_hw_decoding) { + AVHWAccel *hwaccel = find_hwaccel_codec(codec_context); + + if (hwaccel) { + codec_context->opaque = hwaccel; + codec_context->get_format = get_hwaccel_format; + AVCodec *codec_vda = + avcodec_find_decoder_by_name(hwaccel->name); + + if (codec_vda != NULL) { + ret = avcodec_open2(codec_context, codec_vda, + &options_dict); + if (ret < 0) { + av_log(NULL, AV_LOG_WARNING, + "no hardware decoder found for" + " codec with id %d", + codec_context->codec_id); + } else { + codec = codec_vda; + } + } + } + } + + if (codec == NULL) { + codec = avcodec_find_decoder(codec_context->codec_id); + if (codec == NULL) { + av_log(NULL, AV_LOG_WARNING, "no decoder found for" + " codec with id %d", + codec_context->codec_id); + return false; + } + if (avcodec_open2(codec_context, codec, &options_dict) < 0) { + av_log(NULL, AV_LOG_WARNING, "unable to open decoder" + " with codec id %d", + codec_context->codec_id); + return false; + } + } + + return initialize_decoder(demuxer, codec_context, stream); +} + +void ff_demuxer_flush(struct ff_demuxer *demuxer) +{ + if (demuxer->video_decoder != NULL && + demuxer->video_decoder->stream != NULL) { + packet_queue_flush(&demuxer->video_decoder->packet_queue); + packet_queue_put_flush_packet( + &demuxer->video_decoder->packet_queue); + } + + if (demuxer->audio_decoder != NULL && + demuxer->audio_decoder->stream != NULL) { + packet_queue_flush(&demuxer->audio_decoder->packet_queue); + packet_queue_put_flush_packet( + &demuxer->audio_decoder->packet_queue); + } +} + +static bool open_input(struct ff_demuxer *demuxer, + AVFormatContext **format_context) +{ + AVInputFormat *input_format = NULL; + + if (demuxer->input_format != NULL) { + input_format = av_find_input_format(demuxer->input_format); + if (input_format == NULL) + av_log(NULL, AV_LOG_WARNING, "unable to find input " + "format %s", + demuxer->input_format); + } else { + AVIOInterruptCB interrupted_callback; + AVDictionary *io_dictionary = NULL; + + interrupted_callback.callback = demuxer_interrupted_callback; + interrupted_callback.opaque = demuxer; + + if (avio_open2(&demuxer->io_context, demuxer->input, 0, + &interrupted_callback, &io_dictionary) != 0) { + av_log(NULL, AV_LOG_ERROR, + "unable to open location %s\n", + demuxer->input); + return false; + } + } + + if (avformat_open_input(format_context, demuxer->input, + input_format, NULL) != 0) + return false; + + return avformat_find_stream_info(*format_context, NULL) >= 0; +} + +static bool find_and_initialize_stream_decoders(struct ff_demuxer *demuxer) +{ + AVFormatContext *format_context = demuxer->format_context; + unsigned int i; + AVStream *audio_stream = NULL; + AVStream *video_stream = NULL; + + for (i = 0; i < format_context->nb_streams; i++) { + AVCodecContext *codec = format_context->streams[i]->codec; + + if (codec->codec_type == AVMEDIA_TYPE_VIDEO && !video_stream) + video_stream = format_context->streams[i]; + + if (codec->codec_type == AVMEDIA_TYPE_AUDIO && !audio_stream) + audio_stream = format_context->streams[i]; + } + + if (video_stream != NULL) + find_decoder(demuxer, video_stream); + + if (audio_stream != NULL) + find_decoder(demuxer, audio_stream); + + if (demuxer->video_decoder == NULL && demuxer->audio_decoder == NULL) { + return false; + } + + if (!set_clock_sync_type(demuxer)) { + return false; + } + + if (demuxer->audio_decoder != NULL) { + if (ff_callbacks_initialize(&demuxer->audio_callbacks)) { + ff_decoder_start(demuxer->audio_decoder); + } else { + ff_decoder_free(demuxer->audio_decoder); + demuxer->audio_decoder = NULL; + if (!set_clock_sync_type(demuxer)) + return false; + } + } + + if (demuxer->video_decoder != NULL) { + if (ff_callbacks_initialize(&demuxer->video_callbacks)) { + ff_decoder_start(demuxer->video_decoder); + } else { + ff_decoder_free(demuxer->video_decoder); + demuxer->video_decoder = NULL; + if (!set_clock_sync_type(demuxer)) + return false; + } + } + + return set_clock_sync_type(demuxer); +} + +static bool handle_seek(struct ff_demuxer *demuxer) +{ + int ret; + + if (demuxer->seek_request) { + AVStream *seek_stream = NULL; + int64_t seek_target = demuxer->seek_pos; + + if (demuxer->video_decoder != NULL) { + seek_stream = demuxer->video_decoder->stream; + + } else if (demuxer->audio_decoder != NULL) { + seek_stream = demuxer->audio_decoder->stream; + } + + if (seek_stream != NULL) { + seek_target = av_rescale_q(seek_target, + AV_TIME_BASE_Q, + seek_stream->time_base); + } + + ret = av_seek_frame(demuxer->format_context, + 0, seek_target, + demuxer->seek_flags); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "unable to seek stream: %s", + av_err2str(ret)); + demuxer->seek_pos = 0; + demuxer->seek_request = false; + return false; + + } else { + if (demuxer->seek_flush) + ff_demuxer_flush(demuxer); + } + + demuxer->seek_request = false; + } + return true; +} + +static void seek_beginning(struct ff_demuxer *demuxer) +{ + demuxer->seek_flags = AVSEEK_FLAG_BACKWARD; + demuxer->seek_pos = demuxer->format_context->start_time; + demuxer->seek_request = true; + demuxer->seek_flush = false; + av_log(NULL, AV_LOG_VERBOSE, "looping media %s", demuxer->input); +} + +static void *demux_thread(void *opaque) +{ + struct ff_demuxer *demuxer = (struct ff_demuxer *) opaque; + int result; + + AVPacket packet; + + if (!open_input(demuxer, &demuxer->format_context)) + goto fail; + + av_dump_format(demuxer->format_context, 0, demuxer->input, 0); + + if (!find_and_initialize_stream_decoders(demuxer)) + goto fail; + + while (!demuxer->abort) { + // failed to seek (looping?) + if (!handle_seek(demuxer)) + break; + + if (ff_decoder_full(demuxer->audio_decoder) || + ff_decoder_full(demuxer->video_decoder)) { + av_usleep(10 * 1000); // 10ms + continue; + } + + result = av_read_frame(demuxer->format_context, &packet); + if (result < 0) { + bool eof = false; + if (result == AVERROR_EOF) { + eof = true; + } else if (demuxer->format_context->pb != NULL) { + AVIOContext *io_context = + demuxer->format_context->pb; + if (io_context->error == 0) { + av_usleep(100 * 1000); // 100ms + continue; + } else { + if (io_context->eof_reached != 0) + eof = true; + } + } + + if (eof) { + if (demuxer->options.is_looping) { + seek_beginning(demuxer); + } else { + break; + } + continue; + } else { + av_log(NULL, AV_LOG_ERROR, + "av_read_frame() failed: %s", + av_err2str(result)); + break; + } + } + + if (ff_decoder_accept(demuxer->video_decoder, &packet)) + continue; + else if (ff_decoder_accept(demuxer->audio_decoder, &packet)) + continue; + else + av_free_packet(&packet); + } + if (demuxer->audio_decoder != NULL) + demuxer->audio_decoder->eof = true; + if (demuxer->video_decoder != NULL) + demuxer->video_decoder->eof = true; +fail: + demuxer->abort = true; + + return NULL; +} diff --git a/deps/libff/libff/ff-demuxer.h b/deps/libff/libff/ff-demuxer.h new file mode 100644 index 000000000..938114f0a --- /dev/null +++ b/deps/libff/libff/ff-demuxer.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "ff-circular-queue.h" +#include "ff-decoder.h" +#include "ff-packet-queue.h" + +#include + +#define FF_DEMUXER_FAIL -1 +#define FF_DEMUXER_SUCCESS 1 + +#include "ff-callbacks.h" + +struct ff_demuxer_options +{ + int audio_packet_queue_size; + int video_packet_queue_size; + int audio_frame_queue_size; + int video_frame_queue_size; + bool is_hw_decoding; + bool is_looping; +}; + +typedef struct ff_demuxer_options ff_demuxer_options_t; + +struct ff_demuxer { + AVIOContext *io_context; + AVFormatContext *format_context; + + struct ff_clock clock; + + struct ff_demuxer_options options; + + struct ff_decoder *audio_decoder; + struct ff_callbacks audio_callbacks; + + struct ff_decoder *video_decoder; + struct ff_callbacks video_callbacks; + + pthread_t demuxer_thread; + + int64_t seek_pos; + bool seek_request; + int seek_flags; + bool seek_flush; + + bool abort; + + char *input; + char *input_format; +}; + +typedef struct ff_demuxer ff_demuxer_t; + +struct ff_demuxer *ff_demuxer_init(); +bool ff_demuxer_open(struct ff_demuxer *demuxer, char *input, char *input_format); +void ff_demuxer_free(struct ff_demuxer *demuxer); + +void ff_demuxer_set_callbacks(struct ff_callbacks *callbacks, + ff_callback_frame frame, + ff_callback_format format, + ff_callback_initialize initialize, + ff_callback_frame frame_initialize, + ff_callback_frame frame_free, + void *opaque); + +void ff_demuxer_flush(struct ff_demuxer *demuxer); diff --git a/deps/libff/libff/ff-frame.h b/deps/libff/libff/ff-frame.h new file mode 100644 index 000000000..091967cac --- /dev/null +++ b/deps/libff/libff/ff-frame.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include + +struct ff_frame { + AVFrame *frame; + void *opaque; + double pts; + int64_t duration; +}; + +typedef struct ff_frame ff_frame_t; diff --git a/deps/libff/libff/ff-packet-queue.c b/deps/libff/libff/ff-packet-queue.c new file mode 100644 index 000000000..e1f6350fc --- /dev/null +++ b/deps/libff/libff/ff-packet-queue.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-packet-queue.h" + +bool packet_queue_init(struct ff_packet_queue *q) +{ + memset(q, 0, sizeof(struct ff_packet_queue)); + + if (pthread_mutex_init(&q->mutex, NULL) != 0) + goto fail; + + if (pthread_cond_init(&q->cond, NULL) != 0) + goto fail1; + + av_init_packet(&q->flush_packet); + q->flush_packet.data = (unsigned char *) "FLUSH"; + + return true; + +fail1: + pthread_mutex_destroy(&q->mutex); +fail: + return false; + +} + +void packet_queue_abort(struct ff_packet_queue *q) +{ + pthread_mutex_lock(&q->mutex); + q->abort = true; + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); +} + +void packet_queue_free(struct ff_packet_queue *q) +{ + packet_queue_flush(q); + + pthread_mutex_destroy(&q->mutex); + pthread_cond_destroy(&q->cond); + + av_free_packet(&q->flush_packet); +} + +int packet_queue_put(struct ff_packet_queue *q, AVPacket *packet) +{ + AVPacketList *new_packet; + + if (packet != &q->flush_packet && av_dup_packet(packet) < 0) + return FF_PACKET_FAIL; + + new_packet = av_malloc(sizeof(AVPacketList)); + + if (new_packet == NULL) + return FF_PACKET_FAIL; + + new_packet->pkt = *packet; + new_packet->next = NULL; + + pthread_mutex_lock(&q->mutex); + + if (q->last_packet == NULL) + q->first_packet = new_packet; + else + q->last_packet->next = new_packet; + + q->last_packet = new_packet; + + q->count++; + q->total_size += new_packet->pkt.size; + + pthread_cond_signal(&q->cond); + pthread_mutex_unlock(&q->mutex); + + return FF_PACKET_SUCCESS; +} + +int packet_queue_put_flush_packet(struct ff_packet_queue *q) +{ + return packet_queue_put(q, &q->flush_packet); +} + +int packet_queue_get(struct ff_packet_queue *q, AVPacket *packet, bool block) +{ + AVPacketList *potential_packet; + int return_status; + + pthread_mutex_lock(&q->mutex); + + while (true) { + potential_packet = q->first_packet; + + if (potential_packet != NULL) { + q->first_packet = potential_packet->next; + + if (q->first_packet == NULL) + q->last_packet = NULL; + + q->count--; + q->total_size -= potential_packet->pkt.size; + *packet = potential_packet->pkt; + av_free(potential_packet); + return_status = FF_PACKET_SUCCESS; + break; + + } else if (!block) { + return_status = FF_PACKET_EMPTY; + break; + + } else { + pthread_cond_wait(&q->cond, &q->mutex); + if (q->abort) { + return_status = FF_PACKET_FAIL; + break; + } + } + } + + pthread_mutex_unlock(&q->mutex); + + return return_status; +} + +void packet_queue_flush(struct ff_packet_queue *q) +{ + AVPacketList *packet; + + pthread_mutex_lock(&q->mutex); + + for (packet = q->first_packet; packet != NULL; + packet = q->first_packet) { + q->first_packet = packet->next; + av_free_packet(&packet->pkt); + av_freep(&packet); + } + + q->last_packet = q->first_packet = NULL; + q->count = 0; + q->total_size = 0; + + pthread_mutex_unlock(&q->mutex); +} diff --git a/deps/libff/libff/ff-packet-queue.h b/deps/libff/libff/ff-packet-queue.h new file mode 100644 index 000000000..d91e829be --- /dev/null +++ b/deps/libff/libff/ff-packet-queue.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +#define FF_PACKET_FAIL -1 +#define FF_PACKET_EMPTY 0 +#define FF_PACKET_SUCCESS 1 + +struct ff_packet_queue { + AVPacketList *first_packet; + AVPacketList *last_packet; + pthread_mutex_t mutex; + pthread_cond_t cond; + AVPacket flush_packet; + int count; + unsigned int total_size; + bool abort; +}; + +typedef struct ff_packet_queue ff_packet_queue_t; + +bool packet_queue_init(struct ff_packet_queue *q); +void packet_queue_abort(struct ff_packet_queue *q); +void packet_queue_free(struct ff_packet_queue *q); +int packet_queue_put(struct ff_packet_queue *q, AVPacket *packet); +int packet_queue_put_flush_packet(struct ff_packet_queue *q); +int packet_queue_get(struct ff_packet_queue *q, AVPacket *packet, bool block); + +void packet_queue_flush(struct ff_packet_queue *q); diff --git a/deps/libff/libff/ff-timer.c b/deps/libff/libff/ff-timer.c new file mode 100644 index 000000000..5fd9d663c --- /dev/null +++ b/deps/libff/libff/ff-timer.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-timer.h" + +#include +#include +#include +#include + +static void *timer_thread(void *opaque) +{ + struct ff_timer *timer = (struct ff_timer *)opaque; + int ret; + + while (true) { + pthread_mutex_lock(&timer->mutex); + + if (timer->abort) { + pthread_mutex_unlock(&timer->mutex); + break; + } + + uint64_t current_time = av_gettime(); + if (current_time < timer->next_wake) { + int64_t sleep_time_us = timer->next_wake - current_time; + + struct timespec sleep_time; + sleep_time.tv_sec = + (long)sleep_time_us / AV_TIME_BASE; + sleep_time.tv_nsec = + (long)(sleep_time_us % AV_TIME_BASE) + * 1000; + + ret = pthread_cond_timedwait(&timer->cond, + &timer->mutex, &sleep_time); + if (ret != 0) { + // failed to wait, just sleep + av_usleep((unsigned)sleep_time_us); + } + + // we can be woken up merely to set a sooner wake time + + } else { + // no new next_wake, sleep until we get something + av_usleep(1000); + } + + // we woke up for some reason + current_time = av_gettime(); + if (timer->next_wake <= current_time || timer->needs_wake) { + timer->callback(timer->opaque); + timer->needs_wake = false; + } + + pthread_mutex_unlock(&timer->mutex); + } + + return NULL; +} + +bool ff_timer_init(struct ff_timer *timer, ff_timer_callback callback, + void *opaque) +{ + int ret; + + memset(timer, 0, sizeof(struct ff_timer)); + timer->abort = false; + timer->callback = callback; + timer->opaque = opaque; + + pthread_mutexattr_init(&timer->mutexattr); + pthread_mutexattr_settype(&timer->mutexattr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&timer->mutex, &timer->mutexattr); + pthread_cond_init(&timer->cond, NULL); + + ret = pthread_create(&timer->timer_thread, NULL, timer_thread, timer); + return ret == 0; +} + +void ff_timer_free(struct ff_timer *timer) +{ + void *thread_result; + + assert(timer != NULL); + + pthread_cancel(timer->timer_thread); + pthread_join(timer->timer_thread, &thread_result); + + pthread_mutex_destroy(&timer->mutex); + pthread_mutexattr_destroy(&timer->mutexattr); + pthread_cond_destroy(&timer->cond); +} + +void ff_timer_schedule(struct ff_timer *timer, uint64_t microseconds) +{ + uint64_t cur_time = av_gettime(); + uint64_t new_wake_time = cur_time + microseconds; + + pthread_mutex_lock(&timer->mutex); + + timer->needs_wake = true; + if (new_wake_time < timer->next_wake || cur_time > timer->next_wake) + timer->next_wake = new_wake_time; + + pthread_cond_signal(&timer->cond); + + pthread_mutex_unlock(&timer->mutex); +} diff --git a/deps/libff/libff/ff-timer.h b/deps/libff/libff/ff-timer.h new file mode 100644 index 000000000..7dd6f1a06 --- /dev/null +++ b/deps/libff/libff/ff-timer.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include +#include + +typedef void (*ff_timer_callback)(void *opaque); + +struct ff_timer { + ff_timer_callback callback; + void *opaque; + + pthread_mutex_t mutex; + pthread_mutexattr_t mutexattr; + pthread_cond_t cond; + + pthread_t timer_thread; + uint64_t next_wake; + bool needs_wake; + + bool abort; +}; + +typedef struct ff_timer ff_timer_t; + +bool ff_timer_init(struct ff_timer *timer, + ff_timer_callback callback, void *opaque); +void ff_timer_free(struct ff_timer *timer); +void ff_timer_schedule(struct ff_timer *timer, uint64_t microseconds); diff --git a/deps/libff/libff/ff-video-decoder.c b/deps/libff/libff/ff-video-decoder.c new file mode 100644 index 000000000..ce4ae0a1f --- /dev/null +++ b/deps/libff/libff/ff-video-decoder.c @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2015 John R. Bradley + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ff-callbacks.h" +#include "ff-circular-queue.h" +#include "ff-clock.h" +#include "ff-decoder.h" +#include "ff-frame.h" +#include "ff-packet-queue.h" +#include "ff-timer.h" + +#include +#include + +#include + +static bool queue_frame(struct ff_decoder *decoder, AVFrame *frame, + double best_effort_pts) +{ + struct ff_frame *queue_frame; + bool call_initialize; + + ff_circular_queue_wait_write(&decoder->frame_queue); + + if (decoder->abort) { + return false; + } + + queue_frame = ff_circular_queue_peek_write(&decoder->frame_queue); + + // Check if we need to communicate a different format has been received + // to any callbacks + AVCodecContext *codec = decoder->codec; + call_initialize = (queue_frame->frame == NULL + || queue_frame->frame->width != codec->width + || queue_frame->frame->height != codec->height + || queue_frame->frame->format != codec->pix_fmt); + + if (queue_frame->frame != NULL) + av_frame_free(&queue_frame->frame); + + queue_frame->frame = av_frame_clone(frame); + + if (call_initialize) + ff_callbacks_frame_initialize(queue_frame, decoder->callbacks); + + queue_frame->pts = best_effort_pts; + + ff_circular_queue_advance_write(&decoder->frame_queue, queue_frame); + + return true; +} + +void *ff_video_decoder_thread(void *opaque_video_decoder) +{ + struct ff_decoder *decoder = (struct ff_decoder*)opaque_video_decoder; + + AVPacket packet = {0}; + int complete; + AVFrame *frame = av_frame_alloc(); + int ret; + + while (!decoder->abort) { + ret = packet_queue_get(&decoder->packet_queue, &packet, 1); + if (ret == FF_PACKET_FAIL) { + // should we just use abort here? + break; + } + + if (packet.data == decoder->packet_queue.flush_packet.data) { + avcodec_flush_buffers(decoder->codec); + continue; + } + + avcodec_decode_video2(decoder->codec, frame, + &complete, &packet); + + // Did we get an entire video frame? This doesn't guarantee + // there is a picture to show for some codecs, but we still want + // to adjust our various internal clocks for the next frame + if (complete) { + + // If we don't have a good PTS, try to guess based + // on last received PTS provided plus prediction + // This function returns a pts scaled to stream + // time base + double best_effort_pts = + ff_decoder_get_best_effort_pts(decoder, frame); + + queue_frame(decoder, frame, best_effort_pts); + av_frame_unref(frame); + } + + av_free_packet(&packet); + } + + av_frame_free(&frame); + return NULL; +}