From 5d6a4e5172b63963df87fa3ace76e2e91f1e00c8 Mon Sep 17 00:00:00 2001 From: kc5nra Date: Sun, 8 Mar 2015 13:42:23 -0500 Subject: [PATCH 1/3] deps-libff: Add libff library to deps This library wraps the ffmpeg library and adds some utility functions and types. --- deps/CMakeLists.txt | 1 + deps/libff/CMakeLists.txt | 52 +++ deps/libff/libff/ff-audio-decoder.c | 156 ++++++++ deps/libff/libff/ff-callbacks.c | 61 ++++ deps/libff/libff/ff-callbacks.h | 46 +++ deps/libff/libff/ff-circular-queue.c | 140 +++++++ deps/libff/libff/ff-circular-queue.h | 50 +++ deps/libff/libff/ff-clock.c | 22 ++ deps/libff/libff/ff-clock.h | 38 ++ deps/libff/libff/ff-decoder.c | 292 +++++++++++++++ deps/libff/libff/ff-decoder.h | 70 ++++ deps/libff/libff/ff-demuxer.c | 522 +++++++++++++++++++++++++++ deps/libff/libff/ff-demuxer.h | 83 +++++ deps/libff/libff/ff-frame.h | 28 ++ deps/libff/libff/ff-packet-queue.c | 156 ++++++++ deps/libff/libff/ff-packet-queue.h | 47 +++ deps/libff/libff/ff-timer.c | 122 +++++++ deps/libff/libff/ff-timer.h | 45 +++ deps/libff/libff/ff-video-decoder.c | 112 ++++++ 19 files changed, 2043 insertions(+) create mode 100644 deps/libff/CMakeLists.txt create mode 100644 deps/libff/libff/ff-audio-decoder.c create mode 100644 deps/libff/libff/ff-callbacks.c create mode 100644 deps/libff/libff/ff-callbacks.h create mode 100644 deps/libff/libff/ff-circular-queue.c create mode 100644 deps/libff/libff/ff-circular-queue.h create mode 100644 deps/libff/libff/ff-clock.c create mode 100644 deps/libff/libff/ff-clock.h create mode 100644 deps/libff/libff/ff-decoder.c create mode 100644 deps/libff/libff/ff-decoder.h create mode 100644 deps/libff/libff/ff-demuxer.c create mode 100644 deps/libff/libff/ff-demuxer.h create mode 100644 deps/libff/libff/ff-frame.h create mode 100644 deps/libff/libff/ff-packet-queue.c create mode 100644 deps/libff/libff/ff-packet-queue.h create mode 100644 deps/libff/libff/ff-timer.c create mode 100644 deps/libff/libff/ff-timer.h create mode 100644 deps/libff/libff/ff-video-decoder.c 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; +} From 8a32345071a72aab913d3828bf2bb367a5c3165e Mon Sep 17 00:00:00 2001 From: John Bradley Date: Wed, 4 Mar 2015 12:41:18 -0600 Subject: [PATCH 2/3] obs-ffmpeg: Add FFmpeg format -> obs format func --- plugins/obs-ffmpeg/obs-ffmpeg-formats.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-formats.h b/plugins/obs-ffmpeg/obs-ffmpeg-formats.h index 377cfbf67..ad3918e4f 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-formats.h +++ b/plugins/obs-ffmpeg/obs-ffmpeg-formats.h @@ -25,6 +25,21 @@ static inline enum AVPixelFormat obs_to_ffmpeg_video_format( return AV_PIX_FMT_NONE; } +static inline enum video_format ffmpeg_to_obs_video_format( + enum AVPixelFormat format) +{ + switch (format) { + case AV_PIX_FMT_YUV420P: return VIDEO_FORMAT_I420; + case AV_PIX_FMT_NV12: return VIDEO_FORMAT_NV12; + case AV_PIX_FMT_YUYV422: return VIDEO_FORMAT_YUY2; + case AV_PIX_FMT_UYVY422: return VIDEO_FORMAT_UYVY; + case AV_PIX_FMT_RGBA: return VIDEO_FORMAT_RGBA; + case AV_PIX_FMT_BGRA: return VIDEO_FORMAT_BGRA; + case AV_PIX_FMT_NONE: + default: return VIDEO_FORMAT_NONE; + } +} + static inline enum audio_format convert_ffmpeg_sample_format( enum AVSampleFormat format) { From 2fc6c4d725d5d3a0b608232c6bb2ff03a5815a3f Mon Sep 17 00:00:00 2001 From: John Bradley Date: Wed, 4 Mar 2015 12:45:50 -0600 Subject: [PATCH 3/3] obs-ffmpeg: Add FFMpeg input source --- plugins/obs-ffmpeg/CMakeLists.txt | 8 +- plugins/obs-ffmpeg/data/locale/en-US.ini | 11 + plugins/obs-ffmpeg/obs-ffmpeg-source.c | 423 +++++++++++++++++++++++ plugins/obs-ffmpeg/obs-ffmpeg.c | 6 + 4 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 plugins/obs-ffmpeg/obs-ffmpeg-source.c diff --git a/plugins/obs-ffmpeg/CMakeLists.txt b/plugins/obs-ffmpeg/CMakeLists.txt index a29d81a8a..98e2b8722 100644 --- a/plugins/obs-ffmpeg/CMakeLists.txt +++ b/plugins/obs-ffmpeg/CMakeLists.txt @@ -6,7 +6,7 @@ if(MSVC) endif() find_package(FFMpeg REQUIRED - COMPONENTS avcodec avutil swscale avformat swresample) + COMPONENTS avcodec avfilter avdevice avutil swscale avformat swresample) include_directories(${FFMPEG_INCLUDE_DIRS}) set(obs-ffmpeg_HEADERS @@ -16,13 +16,15 @@ set(obs-ffmpeg_HEADERS set(obs-ffmpeg_SOURCES obs-ffmpeg.c obs-ffmpeg-aac.c - obs-ffmpeg-output.c) - + obs-ffmpeg-output.c + obs-ffmpeg-source.c) + add_library(obs-ffmpeg MODULE ${obs-ffmpeg_HEADERS} ${obs-ffmpeg_SOURCES}) target_link_libraries(obs-ffmpeg libobs + libff ${obs-ffmpeg_PLATFORM_DEPS} ${FFMPEG_LIBRARIES}) diff --git a/plugins/obs-ffmpeg/data/locale/en-US.ini b/plugins/obs-ffmpeg/data/locale/en-US.ini index e34a28690..df3d306e1 100644 --- a/plugins/obs-ffmpeg/data/locale/en-US.ini +++ b/plugins/obs-ffmpeg/data/locale/en-US.ini @@ -1,3 +1,14 @@ FFmpegOutput="FFmpeg Output" FFmpegAAC="FFmpeg Default AAC Encoder" Bitrate="Bitrate" + +FFmpegSource="Media Source" +LocalFile="Local File" +Looping="Loop" +Input="Input" +InputFormat="Input Format" +ForceFormat="Force format conversion" +HardwareDecode="Use hardware decoding when available" +Advanced="Advanced" +AudioBufferSize="Audio Buffer Size (frames)" +VideoBufferSize="Video Buffer Size (frames)" diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-source.c b/plugins/obs-ffmpeg/obs-ffmpeg-source.c new file mode 100644 index 000000000..c389e11ed --- /dev/null +++ b/plugins/obs-ffmpeg/obs-ffmpeg-source.c @@ -0,0 +1,423 @@ +/* + * 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 +#include + +#include "obs-ffmpeg-compat.h" +#include "obs-ffmpeg-formats.h" + +#include + +#include + +static bool video_frame(struct ff_frame *frame, void *opaque); +static bool video_format(AVCodecContext *codec_context, void *opaque); + +static void ffmpeg_log_callback(void* context, int level, const char* format, + va_list args) +{ + UNUSED_PARAMETER(context); + int obs_level; + + switch (level) { + case AV_LOG_PANIC: + case AV_LOG_FATAL: + obs_level = LOG_ERROR; + break; + case AV_LOG_ERROR: + case AV_LOG_WARNING: + obs_level = LOG_WARNING; + break; + case AV_LOG_INFO: + case AV_LOG_VERBOSE: + obs_level = LOG_INFO; + break; + case AV_LOG_DEBUG: + default: + obs_level = LOG_DEBUG; + } + + blogva(obs_level, format, args); +} + +void initialize_ffmpeg_source() +{ + av_log_set_callback(ffmpeg_log_callback); +} + +struct ffmpeg_source { + struct ff_demuxer *demuxer; + struct SwsContext *sws_ctx; + obs_source_t *source; + enum AVPixelFormat format; + bool is_forcing_scale; + bool is_hw_decoding; +}; + +static bool set_obs_frame_colorprops(struct ff_frame *frame, + struct obs_source_frame *obs_frame) +{ + enum AVColorSpace frame_cs = av_frame_get_colorspace(frame->frame); + enum video_colorspace obs_cs; + + switch(frame_cs) { + case AVCOL_SPC_BT709: obs_cs = VIDEO_CS_709; break; + case AVCOL_SPC_BT470BG: obs_cs = VIDEO_CS_601; break; + case AVCOL_SPC_UNSPECIFIED: obs_cs = VIDEO_CS_DEFAULT; break; + default: + blog(LOG_WARNING, "frame using an unsupported colorspace %d", + frame_cs); + return false; + } + + enum video_range_type range; + obs_frame->format = ffmpeg_to_obs_video_format(frame->frame->format); + obs_frame->full_range = + frame->frame->color_range == AVCOL_RANGE_JPEG; + + range = obs_frame->full_range ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; + + if (!video_format_get_parameters(obs_cs, + range, obs_frame->color_matrix, + obs_frame->color_range_min, + obs_frame->color_range_max)) { + blog(LOG_ERROR, "Failed to get video format " + "parameters for video format %u", + obs_cs); + return false; + } + + return true; +} + +static bool video_frame_scale(struct ff_frame *frame, + struct ffmpeg_source *s, struct obs_source_frame *obs_frame) +{ + int linesize = frame->frame->width * 4; + uint8_t *picture_data = + malloc(linesize * frame->frame->height); + sws_scale( + s->sws_ctx, + (uint8_t const *const *)frame->frame->data, + frame->frame->linesize, + 0, + frame->frame->height, + &picture_data, + &linesize + ); + + obs_frame->data[0] = picture_data; + obs_frame->linesize[0] = linesize; + obs_frame->format = VIDEO_FORMAT_BGRA; + + obs_source_output_video(s->source, obs_frame); + + free(picture_data); + + return true; +} + +static bool video_frame_hwaccel(struct ff_frame *frame, + obs_source_t *source, struct obs_source_frame *obs_frame) +{ + // 4th plane is pixelbuf reference for mac + for (int i = 0; i < 3; i++) { + obs_frame->data[i] = frame->frame->data[i]; + obs_frame->linesize[i] = frame->frame->linesize[i]; + } + + if (!set_obs_frame_colorprops(frame, obs_frame)) + return false; + + obs_source_output_video(source, obs_frame); + return true; +} + +static bool video_frame_direct(struct ff_frame *frame, + struct ffmpeg_source *s, struct obs_source_frame *obs_frame) +{ + int i; + + for (i = 0; i < MAX_AV_PLANES; i++) { + obs_frame->data[i] = frame->frame->data[i]; + obs_frame->linesize[i] = frame->frame->linesize[i]; + } + + if (!set_obs_frame_colorprops(frame, obs_frame)) + return false; + + obs_source_output_video(s->source, obs_frame); + return true; +} + +static bool video_frame(struct ff_frame *frame, void *opaque) +{ + struct ffmpeg_source *s = opaque; + struct obs_source_frame obs_frame = {0}; + + double d_pts = ff_get_sync_clock(&s->demuxer->clock) - frame->pts; + uint64_t pts = os_gettime_ns() - (uint64_t)(d_pts * 1000000000.0L); + + obs_frame.timestamp = pts; + obs_frame.width = frame->frame->width; + obs_frame.height = frame->frame->height; + + if (s->sws_ctx != NULL) + return video_frame_scale(frame, s, &obs_frame); + else if (s->is_hw_decoding) + return video_frame_hwaccel(frame, s->source, &obs_frame); + else + return video_frame_direct(frame, s, &obs_frame); +} + +static bool audio_frame(struct ff_frame *frame, void *opaque) +{ + struct ffmpeg_source *s = opaque; + + struct obs_source_audio audio_data = {0}; + + double d_pts = ff_get_sync_clock(&s->demuxer->clock) - frame->pts; + uint64_t pts = os_gettime_ns() - (uint64_t)(d_pts * 1000000000.0L); + + int channels = av_get_channel_layout_nb_channels( + av_frame_get_channel_layout(frame->frame)); + + for(int i = 0; i < channels; i++) + audio_data.data[i] = frame->frame->data[i]; + + audio_data.samples_per_sec = frame->frame->sample_rate; + audio_data.frames = frame->frame->nb_samples; + audio_data.timestamp = pts; + audio_data.format = + convert_ffmpeg_sample_format(frame->frame->format); + audio_data.speakers = channels; + + obs_source_output_audio(s->source, &audio_data); + + return true; +} + +static bool video_format(AVCodecContext *codec_context, void *opaque) +{ + struct ffmpeg_source *s = opaque; + if (s->sws_ctx) + sws_freeContext(s->sws_ctx); + + enum video_format obs_video_format = + ffmpeg_to_obs_video_format(codec_context->pix_fmt); + + if (obs_video_format == VIDEO_FORMAT_NONE || s->is_forcing_scale) { + s->sws_ctx = sws_getContext( + codec_context->width, + codec_context->height, + codec_context->pix_fmt, + codec_context->width, + codec_context->height, + AV_PIX_FMT_BGRA, + SWS_BILINEAR, + NULL, + NULL, + NULL + ); + } + + return true; +} + +static bool is_local_file_modified(obs_properties_t *props, + obs_property_t *prop, obs_data_t *settings) +{ + UNUSED_PARAMETER(prop); + + bool enabled = obs_data_get_bool(settings, "is_local_file"); + obs_property_t *input = obs_properties_get(props, "input"); + obs_property_t *input_format =obs_properties_get(props, + "input_format"); + obs_property_t *local_file = obs_properties_get(props, "local_file"); + obs_property_t *looping = obs_properties_get(props, "looping"); + obs_property_set_visible(input, !enabled); + obs_property_set_visible(input_format, !enabled); + obs_property_set_visible(local_file, enabled); + obs_property_set_visible(looping, enabled); + + return true; +} + +static bool is_advanced_modified(obs_properties_t *props, + obs_property_t *prop, obs_data_t *settings) +{ + UNUSED_PARAMETER(prop); + + bool enabled = obs_data_get_bool(settings, "advanced"); + obs_property_t *abuf = obs_properties_get(props, "audio_buffer_size"); + obs_property_t *vbuf = obs_properties_get(props, "video_buffer_size"); + obs_property_set_visible(abuf, enabled); + obs_property_set_visible(vbuf, enabled); + + return true; +} + +static obs_properties_t *ffmpeg_source_getproperties(void *data) +{ + UNUSED_PARAMETER(data); + + obs_properties_t *props = obs_properties_create(); + obs_property_t *prop; + // use this when obs allows non-readonly paths + prop = obs_properties_add_bool(props, "is_local_file", + obs_module_text("LocalFile")); + + obs_property_set_modified_callback(prop, is_local_file_modified); + + obs_properties_add_path(props, "local_file", + obs_module_text("LocalFile"), OBS_PATH_FILE, "*.*", + NULL); + + obs_properties_add_bool(props, "looping", obs_module_text("Looping")); + + obs_properties_add_text(props, "input", + obs_module_text("Input"), OBS_TEXT_DEFAULT); + + obs_properties_add_text(props, "input_format", + obs_module_text("InputFormat"), OBS_TEXT_DEFAULT); + + obs_properties_add_bool(props, "force_scale", + obs_module_text("ForceFormat")); + + obs_properties_add_bool(props, "hw_decode", + obs_module_text("HardwareDecode")); + + prop = obs_properties_add_bool(props, "advanced", + obs_module_text("Advanced")); + + obs_property_set_modified_callback(prop, is_advanced_modified); + + prop = obs_properties_add_int(props, "audio_buffer_size", + obs_module_text("AudioBufferSize"), 1, 9999, 1); + + obs_property_set_visible(prop, false); + + prop = obs_properties_add_int(props, "video_buffer_size", + obs_module_text("VideoBufferSize"), 1, 9999, 1); + + obs_property_set_visible(prop, false); + + return props; +} + +static void ffmpeg_source_update(void *data, obs_data_t *settings) +{ + struct ffmpeg_source *s = data; + + bool is_local_file = obs_data_get_bool(settings, "is_local_file"); + bool is_advanced = obs_data_get_bool(settings, "advanced"); + + bool is_looping; + char *input; + char *input_format; + + if (is_local_file) { + input = (char *)obs_data_get_string(data, "local_file"); + input_format = NULL; + is_looping = obs_data_get_bool(data, "looping"); + } else { + input = (char *)obs_data_get_string(data, "input"); + input_format = (char *)obs_data_get_string(data, + "input_format"); + is_looping = false; + } + + s->is_forcing_scale = obs_data_get_bool(data, "force_scale"); + s->is_hw_decoding = obs_data_get_bool(data, "hw_decode"); + + if (s->demuxer != NULL) + ff_demuxer_free(s->demuxer); + + if (s->sws_ctx != NULL) { + sws_freeContext(s->sws_ctx); + s->sws_ctx = NULL; + } + + s->demuxer = ff_demuxer_init(); + s->demuxer->options.is_hw_decoding = s->is_hw_decoding; + s->demuxer->options.is_looping = is_looping; + + if (is_advanced) { + int audio_buffer_size = (int)obs_data_get_int(settings, + "audio_buffer_size"); + int video_buffer_size = (int)obs_data_get_int(settings, + "video_buffer_size"); + if (audio_buffer_size < 1) { + audio_buffer_size = 1; + blog(LOG_WARNING, "invalid audio_buffer_size %d", + audio_buffer_size); + } + if (video_buffer_size < 1) { + video_buffer_size = 1; + blog(LOG_WARNING, "invalid audio_buffer_size %d", + audio_buffer_size); + } + s->demuxer->options.audio_frame_queue_size = audio_buffer_size; + s->demuxer->options.video_frame_queue_size = video_buffer_size; + } + + ff_demuxer_set_callbacks(&s->demuxer->video_callbacks, + video_frame, video_format, + NULL, NULL, NULL, s); + + ff_demuxer_set_callbacks(&s->demuxer->audio_callbacks, + audio_frame, NULL, + NULL, NULL, NULL, s); + + ff_demuxer_open(s->demuxer, input, input_format); +} + + +static const char *ffmpeg_source_getname(void) +{ + return obs_module_text("FFMpegSource"); +} + +static void *ffmpeg_source_create(obs_data_t *settings, obs_source_t *source) +{ + UNUSED_PARAMETER(settings); + + struct ffmpeg_source *s = bzalloc(sizeof(struct ffmpeg_source)); + s->source = source; + + ffmpeg_source_update(s, settings); + return s; +} + +static void ffmpeg_source_destroy(void *data) +{ + struct ffmpeg_source *s = data; + + ff_demuxer_free(s->demuxer); + bfree(s); +} + +struct obs_source_info ffmpeg_source = { + .id = "ffmpeg_source", + .type = OBS_SOURCE_TYPE_INPUT, + .output_flags = OBS_SOURCE_ASYNC_VIDEO | OBS_SOURCE_AUDIO, + .get_name = ffmpeg_source_getname, + .create = ffmpeg_source_create, + .destroy = ffmpeg_source_destroy, + .get_properties = ffmpeg_source_getproperties, + .update = ffmpeg_source_update +}; diff --git a/plugins/obs-ffmpeg/obs-ffmpeg.c b/plugins/obs-ffmpeg/obs-ffmpeg.c index 6386e1286..52d904dba 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg.c @@ -3,11 +3,17 @@ OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-ffmpeg", "en-US") +extern struct obs_source_info ffmpeg_source; extern struct obs_output_info ffmpeg_output; extern struct obs_encoder_info aac_encoder_info; +void initialize_ffmpeg_source(); + bool obs_module_load(void) { + initialize_ffmpeg_source(); + + obs_register_source(&ffmpeg_source); obs_register_output(&ffmpeg_output); obs_register_encoder(&aac_encoder_info); return true;