diff options
Diffstat (limited to 'debian/transcode/transcode-1.1.7/src/framebuffer.c')
| -rw-r--r-- | debian/transcode/transcode-1.1.7/src/framebuffer.c | 1275 |
1 files changed, 1275 insertions, 0 deletions
diff --git a/debian/transcode/transcode-1.1.7/src/framebuffer.c b/debian/transcode/transcode-1.1.7/src/framebuffer.c new file mode 100644 index 00000000..4a4d8cdd --- /dev/null +++ b/debian/transcode/transcode-1.1.7/src/framebuffer.c @@ -0,0 +1,1275 @@ +/* + * framebuffer.c -- audio/video frame ringbuffers, reloaded. + * (C) 2005-2010 - Francesco Romani <fromani -at- gmail -dot- com> + * Based on code + * (C) 2001-2006 - Thomas Oestreich. + * + * This file is part of transcode, a video stream processing tool. + * + * transcode is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * transcode is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <pthread.h> + +#include "transcode.h" +#include "tc_defaults.h" +#include "framebuffer.h" +#include "decoder.h" +#include "encoder-common.h" + +#include "libtc/tcframes.h" +#include "libtc/ratiocodes.h" + +/* + * Summary: + * This code acts as generic ringbuffer implementation, with + * specializations for main (audio and video) ringbufffers + * in order to cope legacy constraints from 1.0.x series. + * It replaces former src/{audio,video}_buffer.c in (hopefully!) + * a more generic, clean, maintanable, compact way. + * + * Please note that there is *still* some other ringbuffer + * scatthered through codebase (subtitle buffer,d emux buffers, + * possibly more). They will be merged lately or will be dropped + * or reworked. + * + * This code can, of course, be further improved (and GREATLY improved, + * especially for multithreading safeness), but doing so + * hasn't high priority on my TODO list, I've covered with this + * piece of code most urgent todos for 1.1.0. -- FR + */ + +static pthread_mutex_t aframe_list_lock = PTHREAD_MUTEX_INITIALIZER; +static aframe_list_t *aframe_list_head = NULL; +static aframe_list_t *aframe_list_tail = NULL; +static pthread_cond_t audio_import_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t audio_filter_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t audio_export_cond = PTHREAD_COND_INITIALIZER; + +static pthread_mutex_t vframe_list_lock = PTHREAD_MUTEX_INITIALIZER; +static vframe_list_t *vframe_list_head = NULL; +static vframe_list_t *vframe_list_tail = NULL; +static pthread_cond_t video_import_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t video_filter_cond = PTHREAD_COND_INITIALIZER; +static pthread_cond_t video_export_cond = PTHREAD_COND_INITIALIZER; + +void tc_framebuffer_interrupt_import(void) +{ + pthread_mutex_lock(&aframe_list_lock); + pthread_cond_signal(&audio_import_cond); + pthread_mutex_unlock(&aframe_list_lock); + + pthread_mutex_lock(&vframe_list_lock); + pthread_cond_signal(&video_import_cond); + pthread_mutex_unlock(&vframe_list_lock); +} + +void tc_framebuffer_interrupt(void) +{ + pthread_mutex_lock(&aframe_list_lock); + pthread_cond_signal(&audio_import_cond); + pthread_cond_broadcast(&audio_filter_cond); + /* filter layer deserves special care */ + pthread_cond_signal(&audio_export_cond); + pthread_mutex_unlock(&aframe_list_lock); + + pthread_mutex_lock(&vframe_list_lock); + pthread_cond_signal(&video_import_cond); + pthread_cond_broadcast(&video_filter_cond); + /* filter layer deserves special care */ + pthread_cond_signal(&video_export_cond); + pthread_mutex_unlock(&vframe_list_lock); +} + +/* ------------------------------------------------------------------ */ + +/* + * Layered, custom allocator/disposer for ringbuffer structures. + * The idea is to simplify (from ringbuffer viewpoint!) frame + * allocation/disposal and to make it as much generic as is possible + * (avoif if()s and so on). + */ + +typedef TCFramePtr (*TCFrameAllocFn)(const TCFrameSpecs *); + +typedef void (*TCFrameFreeFn)(TCFramePtr); + +/* ------------------------------------------------------------------ */ + +typedef struct tcringframebuffer_ TCRingFrameBuffer; +struct tcringframebuffer_ { + /* real ringbuffer */ + TCFramePtr *frames; + + /* indexes of ringbuffer */ + int next; + int last; + + /* counters. How many frames in various TCFrameStatus-es? */ + int null; + int empty; + int wait; + int locked; + int ready; + + /* what we need here? */ + const TCFrameSpecs *specs; + /* (de)allocation helpers */ + TCFrameAllocFn alloc; + TCFrameFreeFn free; +}; + +static TCRingFrameBuffer tc_audio_ringbuffer; +static TCRingFrameBuffer tc_video_ringbuffer; + +/* + * Specs used internally. I don't export this structure directly + * because I want to be free to change it if needed + */ +static TCFrameSpecs tc_specs = { + /* Largest supported values, to ensure the buffer is always big enough + * (see FIXME in tc_framebuffer_set_specs()) */ + .frc = 3, // PAL, why not + .width = TC_MAX_V_FRAME_WIDTH, + .height = TC_MAX_V_FRAME_HEIGHT, + .format = TC_CODEC_RGB, + .rate = RATE, + .channels = CHANNELS, + .bits = BITS, + .samples = 48000.0, +}; + +/* + * Frame allocation/disposal helpers, needed by code below + * thin wrappers around libtc facilities + * I don't care about layering and performance loss, *here*, because + * frame are supposed to be allocated/disposed ahead of time, and + * always outside inner (performance-sensitive) loops. + */ + +/* ------------------------------------------------------------------ */ + +#define TCFRAMEPTR_IS_NULL(tcf) (tcf.generic == NULL) + +static TCFramePtr tc_video_alloc(const TCFrameSpecs *specs) +{ + TCFramePtr frame; + frame.video = tc_new_video_frame(specs->width, specs->height, + specs->format, TC_FALSE); + return frame; +} + +static TCFramePtr tc_audio_alloc(const TCFrameSpecs *specs) +{ + TCFramePtr frame; + frame.audio = tc_new_audio_frame(specs->samples, specs->channels, + specs->bits); + return frame; +} + + +static void tc_video_free(TCFramePtr frame) +{ + tc_del_video_frame(frame.video); +} + +static void tc_audio_free(TCFramePtr frame) +{ + tc_del_audio_frame(frame.audio); +} + +/* ------------------------------------------------------------------ */ + +/* exported commodities :) */ + +vframe_list_t *vframe_alloc_single(void) +{ + return tc_new_video_frame(tc_specs.width, tc_specs.height, + tc_specs.format, TC_TRUE); +} + +aframe_list_t *aframe_alloc_single(void) +{ + return tc_new_audio_frame(tc_specs.samples, tc_specs.channels, + tc_specs.bits); +} + +/* ------------------------------------------------------------------ */ + +static void tc_ring_framebuffer_dump_status(const TCRingFrameBuffer *rfb, + const char *id) +{ + tc_log_msg(__FILE__, "%s: null=%i empty=%i wait=%i" + " locked=%i ready=%i", + id, rfb->null, rfb->empty, rfb->wait, + rfb->locked, rfb->ready); +} + + +const TCFrameSpecs *tc_framebuffer_get_specs(void) +{ + return &tc_specs; +} + +/* + * using an <OOP-ism>accessor</OOP-ism> is also justified here + * by the fact that we compute (ahead of time) samples value for + * later usage. + */ +void tc_framebuffer_set_specs(const TCFrameSpecs *specs) +{ + /* silently ignore NULL specs */ + if (specs != NULL) { + double fps; + + /* raw copy first */ + ac_memcpy(&tc_specs, specs, sizeof(TCFrameSpecs)); + + /* restore width/height/bpp + * (FIXME: temp until we have a way to know the max size that will + * be used through the decode/process/encode chain; without + * this, -V yuv420p -y raw -F rgb (e.g.) crashes with a + * buffer overrun) + */ + tc_specs.width = TC_MAX_V_FRAME_WIDTH; + tc_specs.height = TC_MAX_V_FRAME_HEIGHT; + tc_specs.format = TC_CODEC_RGB; + + /* then deduct missing parameters */ + if (tc_frc_code_to_value(tc_specs.frc, &fps) == TC_NULL_MATCH) { + fps = 1.0; /* sane, very worst case value */ + } +/* tc_specs.samples = (double)tc_specs.rate/fps; */ + tc_specs.samples = (double)tc_specs.rate; + /* + * FIXME + * ok, so we use a MUCH larger buffer (big enough to store 1 *second* + * of raw audio, not 1 *frame*) than needed for reasons similar as + * seen for above video. + * Most notably, this helps in keeping buffers large enough to be + * suitable for encoder flush (see encode_lame.c first). + */ + } +} + +/* ------------------------------------------------------------------ */ +/* NEW API, yet private */ +/* ------------------------------------------------------------------ */ + +/* + * Threading notice: + * + * Generic code doesn't use any locking at all (yet). + * That's was a design choice. For clarity, locking is + * provided by back-compatibility wrapper functions, + * or by any other higher-lever caller. + * + * Client code (= outside this code) MUST NEVER used not-thread + * safe code. + */ + + +/* + * tc_init_ring_framebuffer: (NOT thread safe) + * initialize a framebuffer ringbuffer by allocating needed + * amount of frames using given parameters. + * + * Parameters: + * rfb: ring framebuffer structure to initialize. + * specs: frame specifications to use for allocation. + * alloc: frame allocation function to use. + * free: frame disposal function to use. + * size: size of ringbuffer (number of frame to allocate) + * Return Value: + * > 0: wrong (NULL) parameters + * 0: succesfull + * < 0: allocation failed for one or more framesbuffers/internal error + */ +static int tc_init_ring_framebuffer(TCRingFrameBuffer *rfb, + const TCFrameSpecs *specs, + TCFrameAllocFn alloc, + TCFrameFreeFn free, + int size) +{ + if (rfb == NULL || specs == NULL || size < 0 + || alloc == NULL || free == NULL) { + return 1; + } + size = (size > 0) ?size :1; /* allocate at least one frame */ + + rfb->frames = tc_malloc(size * sizeof(TCFramePtr)); + if (rfb->frames == NULL) { + return -1; + } + + rfb->specs = specs; + rfb->alloc = alloc; + rfb->free = free; + + for (rfb->last = 0; rfb->last < size; rfb->last++) { + rfb->frames[rfb->last] = rfb->alloc(rfb->specs); + if (TCFRAMEPTR_IS_NULL(rfb->frames[rfb->last])) { + if (verbose >= TC_DEBUG) { + tc_log_error(__FILE__, "failed frame allocation"); + } + return -1; + } + + rfb->frames[rfb->last].generic->status = TC_FRAME_NULL; + rfb->frames[rfb->last].generic->bufid = rfb->last; + } + + rfb->next = 0; + + rfb->null = size; + rfb->empty = 0; + rfb->wait = 0; + rfb->locked = 0; + rfb->ready = 0; + + if (verbose >= TC_STATS) { + tc_log_info(__FILE__, "allocated %i frames in ringbuffer", size); + } + return 0; +} + +/* + * tc_fini_ring_framebuffer: (NOT thread safe) + * finalize a framebuffer ringbuffer by freeing all acquired + * resources (framebuffer memory). + * + * Parameters: + * rfb: ring framebuffer structure to finalize. + * Return Value: + * None. + */ +static void tc_fini_ring_framebuffer(TCRingFrameBuffer *rfb) +{ + if (rfb != NULL && rfb->free != NULL) { + int i = 0, n = rfb->last; + + for (i = 0; i < rfb->last; i++) { + rfb->free(rfb->frames[i]); + } + tc_free(rfb->frames); + rfb->last = 0; + + if (verbose >= TC_STATS) { + tc_log_info(__FILE__, "freed %i frames in ringbuffer", n); + } + } +} + +/* + * tc_ring_framebuffer_retrieve_frame: (NOT thread safe) + * retrieve next unclaimed (TC_FRAME_NULL) framebuffer from + * ringbuffer and return a pointer to it for later usage + * by client code. + * + * Parameters: + * rfb: ring framebuffer to use + * Return Value: + * Always a framebuffer generic pointer. That can be pointing to + * NULL if there aren't no more unclaimed (TC_FRAME_NULL) framebuffer + * avalaible; otherwise it contains + * a pointer to retrieved framebuffer. + * DO NOT *free() such pointer directly! use + * tc_ring_framebuffer_release_frame() instead! + */ +static TCFramePtr tc_ring_framebuffer_retrieve_frame(TCRingFrameBuffer *rfb) +{ + TCFramePtr ptr; + ptr.generic = NULL; + + if (rfb != NULL) { + int i = 0; + + ptr = rfb->frames[rfb->next]; + for (i = 0; i < rfb->last; i++) { + if (ptr.generic->status == TC_FRAME_NULL) { + break; + } + rfb->next++; + rfb->next %= rfb->last; + ptr = rfb->frames[rfb->next]; + } + + if (ptr.generic->status != TC_FRAME_NULL) { + if (verbose >= TC_FLIST) { + tc_log_warn(__FILE__, "retrieved buffer=%i, but not empty", + ptr.generic->status); + } + ptr.generic = NULL; /* enforce NULL-ness */ + } else { + if (verbose >= TC_FLIST) { + tc_log_msg(__FILE__, "retrieved buffer = %i [%i]", + rfb->next, ptr.generic->bufid); + } + /* adjust internal pointer */ + rfb->null--; + rfb->next++; + rfb->next %= rfb->last; + } + } + return ptr; +} + +/* + * tc_ring_framebuffer_release_frame: (NOT thread safe) + * release a previously retrieved frame back to ringbuffer, + * removing claim from it and making again avalaible (TC_FRAME_NULL). + * + * Parameters: + * rfb: ring framebuffer to use. + * frame: generic pointer to frame to release. + * Return Value: + * > 0: wrong (NULL) parameters. + * 0: succesfull + * < 0: internal error (frame to be released isn't empty). + */ +static int tc_ring_framebuffer_release_frame(TCRingFrameBuffer *rfb, + TCFramePtr frame) +{ + if (rfb == NULL || TCFRAMEPTR_IS_NULL(frame)) { + return 1; + } + if (verbose >= TC_FLIST) { + tc_log_msg(__FILE__, "releasing frame #%i [%i]", + frame.generic->bufid, rfb->next); + } + frame.generic->status = TC_FRAME_NULL; + rfb->null++; + return 0; +} + +/* + * tc_ring_framebuffer_register_frame: (NOT thread safe) + * retrieve and register a framebuffer from a ringbuffer by + * attaching an ID to it, setup properly status and updating + * internal ringbuffer counters. + * + * That's the function that client code is supposed to use + * (maybe wrapped by some thin macros to save status setting troubles). + * In general, dont' use retrieve_frame directly, use register_frame + * instead. + * + * Parameters: + * rfb: ring framebuffer to use + * id: id to attach to registered framebuffer + * status: status of framebuffer to register. This was needed to + * make registering process multi-purpose. + * Return Value: + * Always a generic framebuffer pointer. That can be pointing to NULL + * if there isn't no more framebuffer avalaible on given ringbuffer; + * otherwise, it will point to a valid framebuffer. + */ +static TCFramePtr tc_ring_framebuffer_register_frame(TCRingFrameBuffer *rfb, + int id, int status) +{ + TCFramePtr ptr; + + /* retrive a valid pointer from the pool */ + if (verbose >= TC_FLIST) { + tc_log_msg(__FILE__, "register frame id = %i", id); + } +#ifdef STATBUFFER + ptr = tc_ring_framebuffer_retrieve_frame(rfb); +#else + ptr = rfb->alloc(rfb->specs); +#endif + + if (!TCFRAMEPTR_IS_NULL(ptr)) { + if (status == TC_FRAME_EMPTY) { + rfb->empty++; + /* blank common attributes */ + memset(ptr.generic, 0, sizeof(frame_list_t)); + ptr.generic->id = id; + } else if (status == TC_FRAME_WAIT) { + rfb->wait++; + } + ptr.generic->status = status; + + /* enforce */ + ptr.generic->next = NULL; + ptr.generic->prev = NULL; + + if (verbose >= TC_FLIST) { + tc_ring_framebuffer_dump_status(rfb, "register_frame"); + } + } + return ptr; +} + +/* + * tc_ring_framebuffer_remove_frame: (NOT thread safe) + * De-register and release a given framebuffer; + * also updates internal ringbuffer counters. + * + * That's the function that client code is supposed to use. + * In general, don't use release_frame directly, use remove_frame + * instead. + * + * Parameters: + * rfb: ring framebuffer to use. + * frame: generic pointer to frambuffer to remove. + * Return Value: + * None. + */ +static void tc_ring_framebuffer_remove_frame(TCRingFrameBuffer *rfb, + TCFramePtr frame) +{ + if (rfb != NULL || !TCFRAMEPTR_IS_NULL(frame)) { + if (frame.generic->status == TC_FRAME_READY) { + rfb->ready--; + } + if (frame.generic->status == TC_FRAME_LOCKED) { + rfb->locked--; + } + /* release valid pointer to pool */ +#ifdef STATBUFFER + tc_ring_framebuffer_release_frame(rfb, frame); +#else + rfb->free(frame); +#endif + + if (verbose >= TC_FLIST) { + tc_ring_framebuffer_dump_status(rfb, "remove_frame"); + } + } +} + +/* ------------------------------------------------------------------ */ +/* Backwared-compatible API */ +/* ------------------------------------------------------------------ */ + +int aframe_alloc(int num) +{ + return tc_init_ring_framebuffer(&tc_audio_ringbuffer, &tc_specs, + tc_audio_alloc, tc_audio_free, num); +} + +int vframe_alloc(int num) +{ + return tc_init_ring_framebuffer(&tc_video_ringbuffer, &tc_specs, + tc_video_alloc, tc_video_free, num); +} + + +void aframe_free(void) +{ + tc_fini_ring_framebuffer(&tc_audio_ringbuffer); +} + +void vframe_free(void) +{ + tc_fini_ring_framebuffer(&tc_video_ringbuffer); +} + + +/* ------------------------------------------------------------------ */ + +/* + * Macro VS generic functions like above: + * + * I've used generic code and TCFramePtr in every place I was + * capable to introduce them in a *clean* way without using any + * casting. Of course there is still a lot of room for improvements, + * but back compatibility is an issue too. I'd like to get rid + * of all those macro and swtich to pure generic code of course, + * so this will be improved in future revisions. In the + * meantime, patches and suggestions welcome ;) -- FR + */ + +#define LIST_FRAME_APPEND(ptr, tail) do { \ + if ((tail) != NULL) { \ + (tail)->next = (ptr); \ + (ptr)->prev = (tail); \ + } \ + (tail) = (ptr); \ +} while (0) + +#define LIST_FRAME_INSERT(ptr, head) do { \ + if ((head) == NULL) { \ + (head) = ptr; \ + } \ +} while (0) + + +aframe_list_t *aframe_register(int id) +{ + int interrupted = TC_FALSE; + TCFramePtr frame; + + pthread_mutex_lock(&aframe_list_lock); + + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|register) requesting a new audio frame"); + + while ((!interrupted && tc_import_audio_running()) + && tc_audio_ringbuffer.null == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|register) audio frame not ready, waiting"); + pthread_cond_wait(&audio_import_cond, &aframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|register) audio frame wait ended"); + interrupted = !tc_running(); + } + + if (interrupted) { + frame.audio = NULL; + } else { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "new audio frame ready"); + + frame = tc_ring_framebuffer_register_frame(&tc_audio_ringbuffer, + id, TC_FRAME_EMPTY); + if (!TCFRAMEPTR_IS_NULL(frame)) { + /* + * complete initialization: + * backward-compatible stuff + */ + LIST_FRAME_APPEND(frame.audio, aframe_list_tail); + /* first frame registered must set aframe_list_head */ + LIST_FRAME_INSERT(frame.audio, aframe_list_head); + } + } + pthread_mutex_unlock(&aframe_list_lock); + return frame.audio; +} + +vframe_list_t *vframe_register(int id) +{ + int interrupted = TC_FALSE; + TCFramePtr frame; + + pthread_mutex_lock(&vframe_list_lock); + + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|register) requesting a new video frame"); + + while ((!interrupted && tc_import_video_running()) + && tc_video_ringbuffer.null == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|register) video frame not ready, waiting"); + pthread_cond_wait(&video_import_cond, &vframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|register) video frame wait ended"); + interrupted = !tc_running(); + } + + if (interrupted) { + frame.video = NULL; + } else { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "new video frame ready"); + + frame = tc_ring_framebuffer_register_frame(&tc_video_ringbuffer, + id, TC_FRAME_EMPTY); + if (!TCFRAMEPTR_IS_NULL(frame)) { + /* + * complete initialization: + * backward-compatible stuff + */ + LIST_FRAME_APPEND(frame.video, vframe_list_tail); + /* first frame registered must set vframe_list_head */ + LIST_FRAME_INSERT(frame.video, vframe_list_head); + } + } + pthread_mutex_unlock(&vframe_list_lock); + return frame.video; +} + + +/* ------------------------------------------------------------------ */ + + +#define LIST_FRAME_LINK(ptr, f, tail) do { \ + /* insert after ptr */ \ + (ptr)->next = (f)->next; \ + (f)->next = (ptr); \ + (ptr)->prev = (f); \ + \ + if ((ptr)->next == NULL) { \ + /* must be last ptr in the list */ \ + (tail) = (ptr); \ + } \ +} while (0) + + +aframe_list_t *aframe_dup(aframe_list_t *f) +{ + int interrupted = TC_FALSE; + TCFramePtr frame; + + if (f == NULL) { + tc_log_warn(__FILE__, "aframe_dup: empty frame"); + return NULL; + } + + pthread_mutex_lock(&aframe_list_lock); + + while (!interrupted && tc_audio_ringbuffer.null == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|dup) audio frame not ready, waiting"); + pthread_cond_wait(&audio_import_cond, &aframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|dup) audio frame wait ended"); + interrupted = !tc_running(); + } + + frame = tc_ring_framebuffer_register_frame(&tc_audio_ringbuffer, + 0, TC_FRAME_WAIT); + if (!TCFRAMEPTR_IS_NULL(frame)) { + aframe_copy(frame.audio, f, 1); + + LIST_FRAME_LINK(frame.audio, f, aframe_list_tail); + } + pthread_mutex_unlock(&aframe_list_lock); + return frame.audio; +} + +vframe_list_t *vframe_dup(vframe_list_t *f) +{ + int interrupted = TC_FALSE; + TCFramePtr frame; + + if (f == NULL) { + tc_log_warn(__FILE__, "vframe_dup: empty frame"); + return NULL; + } + + pthread_mutex_lock(&vframe_list_lock); + + while (!interrupted && tc_video_ringbuffer.null == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|dup) video frame not ready, waiting"); + pthread_cond_wait(&video_import_cond, &vframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|dup) video frame wait ended"); + interrupted = !tc_running(); + } + + frame = tc_ring_framebuffer_register_frame(&tc_video_ringbuffer, + 0, TC_FRAME_WAIT); + if (!TCFRAMEPTR_IS_NULL(frame)) { + vframe_copy(frame.video, f, 1); + + LIST_FRAME_LINK(frame.video, f, vframe_list_tail); + } + pthread_mutex_unlock(&vframe_list_lock); + return frame.video; +} + + +/* ------------------------------------------------------------------ */ + +#define LIST_FRAME_REMOVE(ptr, head, tail) do { \ + if ((ptr)->prev != NULL) { \ + ((ptr)->prev)->next = (ptr)->next; \ + } \ + if ((ptr)->next != NULL) { \ + ((ptr)->next)->prev = (ptr)->prev; \ + } \ + \ + if ((ptr) == (tail)) { \ + (tail) = (ptr)->prev; \ + } \ + if ((ptr) == (head)) { \ + (head) = (ptr)->next; \ + } \ +} while (0) + +void aframe_remove(aframe_list_t *ptr) +{ + if (ptr == NULL) { + tc_log_warn(__FILE__, "aframe_remove: given NULL frame pointer"); + } else { + TCFramePtr frame; + frame.audio = ptr; + + pthread_mutex_lock(&aframe_list_lock); + + LIST_FRAME_REMOVE(ptr, aframe_list_head, aframe_list_tail); + + tc_ring_framebuffer_remove_frame(&tc_audio_ringbuffer, frame); + pthread_cond_signal(&audio_import_cond); + + pthread_mutex_unlock(&aframe_list_lock); + } +} + +void vframe_remove(vframe_list_t *ptr) +{ + if (ptr == NULL) { + tc_log_warn(__FILE__, "vframe_remove: given NULL frame pointer"); + } else { + TCFramePtr frame; + frame.video = ptr; + + pthread_mutex_lock(&vframe_list_lock); + + LIST_FRAME_REMOVE(ptr, vframe_list_head, vframe_list_tail); + + tc_ring_framebuffer_remove_frame(&tc_video_ringbuffer, frame); + pthread_cond_signal(&video_import_cond); + + pthread_mutex_unlock(&vframe_list_lock); + } +} + +/* ------------------------------------------------------------------ */ +/* ------------------------------------------------------------------ */ + +static aframe_list_t *aframe_retrieve_nowait(void) +{ + aframe_list_t *ptr = NULL; + pthread_mutex_lock(&aframe_list_lock); + + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "(A|retrieve_nowait) requesting a new audio frame"); + } + if (aframe_list_head != NULL && aframe_list_head->status == TC_FRAME_READY) { + ptr = aframe_list_head; + } + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "got a new audio frame reference: %p", ptr); + } + pthread_mutex_unlock(&aframe_list_lock); + return ptr; +} + + +static vframe_list_t *vframe_retrieve_nowait(void) +{ + vframe_list_t *ptr = NULL; + pthread_mutex_lock(&vframe_list_lock); + + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "(V|retrieve_nowait) requesting a new video frame"); + } + if (vframe_list_head != NULL && vframe_list_head->status == TC_FRAME_READY) { + ptr = vframe_list_head; + } + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "got a new video frame reference: %p", ptr); + } + pthread_mutex_unlock(&vframe_list_lock); + return ptr; +} + + +/* ------------------------------------------------------------------ */ + +aframe_list_t *aframe_retrieve(void) +{ + int interrupted = TC_FALSE; + aframe_list_t *ptr = NULL; + pthread_mutex_lock(&aframe_list_lock); + + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|retrieve) requesting a new audio frame"); + while (!interrupted + && (aframe_list_head == NULL + || aframe_list_head->status != TC_FRAME_READY)) { + if (verbose >= TC_FLIST) { + tc_log_msg(__FILE__, "(A|retrieve) audio frame not ready, waiting"); + tc_ring_framebuffer_dump_status(&tc_audio_ringbuffer, "retrieve"); + } + pthread_cond_wait(&audio_export_cond, &aframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|retrieve) audio wait just ended"); + interrupted = !tc_running(); + } + + if (!interrupted) { + ptr = aframe_list_head; + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "got a new audio frame reference: %p", ptr); + } + pthread_mutex_unlock(&aframe_list_lock); + return ptr; +} + + +vframe_list_t *vframe_retrieve(void) +{ + int interrupted = TC_FALSE; + vframe_list_t *ptr = NULL; + pthread_mutex_lock(&vframe_list_lock); + + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|retrieve) requesting a new video frame"); + while (!interrupted + && (vframe_list_head == NULL + || vframe_list_head->status != TC_FRAME_READY)) { + if (verbose >= TC_FLIST) { + tc_log_msg(__FILE__, "(V|retrieve) video frame not ready, waiting"); + tc_ring_framebuffer_dump_status(&tc_video_ringbuffer, "retrieve"); + } + pthread_cond_wait(&video_export_cond, &vframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|retrieve) video wait just ended"); + interrupted = !tc_running(); + } + + if (!interrupted) { + ptr = vframe_list_head; + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "got a new video frame reference: %p", ptr); + } + pthread_mutex_unlock(&vframe_list_lock); + return ptr; +} + +#undef LIST_FRAME_RETRIEVE + +/* ------------------------------------------------------------------ */ + +void aframe_flush(void) +{ + int i = 0, done = TC_FALSE; + + do { + aframe_list_t *ptr = aframe_retrieve_nowait(); + if (!ptr) { + done = TC_TRUE; + } else { + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, + "flushing audio buffer id=[%i] bufid=[%i]", + ptr->id, ptr->bufid); + } + aframe_remove(ptr); + i++; + } + } while (!done); + + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "flushed %i audio frames", i); + } +} + +void vframe_flush(void) +{ + int i = 0, done = TC_FALSE; + + do { + vframe_list_t *ptr = vframe_retrieve_nowait(); + if (!ptr) { + done = TC_TRUE; + } else { + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, + "flushing video buffer id=[%i] bufid=[%i]", + ptr->id, ptr->bufid); + } + vframe_remove(ptr); + i++; + } + } while (!done); + + if (verbose >= TC_CLEANUP) { + tc_log_msg(__FILE__, "flushed %i video frames", i); + } +} + +void tc_framebuffer_flush(void) +{ + aframe_flush(); + vframe_flush(); +} + + +/* ------------------------------------------------------------------ */ + +#define DEC_COUNTERS(RFB, STATUS) do { \ + if ((STATUS) == TC_FRAME_READY) { \ + (RFB)->ready--; \ + } \ + if ((STATUS) == TC_FRAME_LOCKED) { \ + (RFB)->locked--; \ + } \ + if ((STATUS) == TC_FRAME_WAIT) { \ + (RFB)->wait--; \ + } \ +} while(0) + +#define INC_COUNTERS(RFB, STATUS) do { \ + if ((STATUS) == TC_FRAME_READY) { \ + (RFB)->ready++; \ + } \ + if ((STATUS) == TC_FRAME_LOCKED) { \ + (RFB)->locked++; \ + } \ + if ((STATUS) == TC_FRAME_WAIT) { \ + (RFB)->wait++; \ + } \ +} while(0) + +#define FRAME_SET_STATUS(RFB, PTR, NEW_STATUS) do { \ + DEC_COUNTERS((RFB), (PTR)->status); \ + (PTR)->status = (NEW_STATUS); \ + INC_COUNTERS((RFB), (PTR)->status); \ +} while (0) + +#define FRAME_LOOKUP(RFB, PTR, OLD_STATUS, NEW_STATUS) do { \ + /* move along the chain and check for status */ \ + for (; (PTR) != NULL; (PTR) = (PTR)->next) { \ + if ((PTR)->status == (OLD_STATUS)) { \ + /* found matching frame */ \ + FRAME_SET_STATUS(RFB, PTR, NEW_STATUS); \ + break; \ + } \ + } \ +} while (0) + + +aframe_list_t *aframe_reserve(void) +{ + int interrupted = TC_FALSE; + aframe_list_t *ptr = NULL; + + pthread_mutex_lock(&aframe_list_lock); + + while (!interrupted && tc_audio_ringbuffer.wait == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|reserve) audio frame not ready, waiting"); + pthread_cond_wait(&audio_filter_cond, &aframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(A|reserve) audio wait just ended"); + interrupted = !tc_running(); + } + + if (!interrupted) { + ptr = aframe_list_head; + FRAME_LOOKUP(&tc_audio_ringbuffer, ptr, TC_FRAME_WAIT, TC_FRAME_LOCKED); + } + + pthread_mutex_unlock(&aframe_list_lock); + return ptr; +} + +vframe_list_t *vframe_reserve(void) +{ + int interrupted = TC_FALSE; + vframe_list_t *ptr = NULL; + + pthread_mutex_lock(&vframe_list_lock); + + while (!interrupted && tc_video_ringbuffer.wait == 0) { + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|reserve) video frame not ready, waiting"); + pthread_cond_wait(&video_filter_cond, &vframe_list_lock); + if (verbose >= TC_FLIST) + tc_log_msg(__FILE__, "(V|reserve) video wait just ended"); + interrupted = !tc_running(); + } + + if (!interrupted) { + ptr = vframe_list_head; + FRAME_LOOKUP(&tc_video_ringbuffer, ptr, TC_FRAME_WAIT, TC_FRAME_LOCKED); + } + + pthread_mutex_unlock(&vframe_list_lock); + return ptr; +} + +#undef FRAME_LOOKUP + +/* ------------------------------------------------------------------ */ + + +#define FRAME_SET_EXT_STATUS(RFB, PTR, NEW_STATUS) do { \ + if ((PTR)->status == TC_FRAME_EMPTY) { \ + (RFB)->empty--; \ + } \ + FRAME_SET_STATUS((RFB), (PTR), (NEW_STATUS)); \ + if ((PTR)->status == TC_FRAME_EMPTY) { \ + (RFB)->empty++; \ + } \ +} while (0) + + +void aframe_push_next(aframe_list_t *ptr, int status) +{ + if (ptr == NULL) { + /* a bit more of paranoia */ + tc_log_warn(__FILE__, "aframe_push_next: given NULL frame pointer"); + } else { + pthread_mutex_lock(&aframe_list_lock); + FRAME_SET_EXT_STATUS(&tc_audio_ringbuffer, ptr, status); + + if (status == TC_FRAME_WAIT) { + pthread_cond_signal(&audio_filter_cond); + } else if (status == TC_FRAME_READY && ptr == aframe_list_head) { // XXX + pthread_cond_signal(&audio_export_cond); + } + if (verbose >= TC_FLIST) { + tc_ring_framebuffer_dump_status(&tc_audio_ringbuffer, "push_next"); + } + pthread_mutex_unlock(&aframe_list_lock); + } +} + + +void vframe_push_next(vframe_list_t *ptr, int status) +{ + if (ptr == NULL) { + /* a bit more of paranoia */ + tc_log_warn(__FILE__, "vframe_push_next: given NULL frame pointer"); + } else { + pthread_mutex_lock(&vframe_list_lock); + FRAME_SET_EXT_STATUS(&tc_video_ringbuffer, ptr, status); + + if (status == TC_FRAME_WAIT) { + pthread_cond_signal(&video_filter_cond); + } else if (status == TC_FRAME_READY && ptr == vframe_list_head) { // XXX + pthread_cond_signal(&video_export_cond); + } + if (verbose >= TC_FLIST) { + tc_ring_framebuffer_dump_status(&tc_video_ringbuffer, "push_next"); + } + pthread_mutex_unlock(&vframe_list_lock); + } +} + +#undef FRAME_SET_STATUS +#undef FRAME_SET_EXT_STATUS + +/* ------------------------------------------------------------------ */ + + +void aframe_dump_status(void) +{ + tc_ring_framebuffer_dump_status(&tc_audio_ringbuffer, + "audio buffer status"); +} + +void vframe_dump_status(void) +{ + tc_ring_framebuffer_dump_status(&tc_video_ringbuffer, + "video buffer status"); +} + +int vframe_have_more(void) +{ + int ret; + pthread_mutex_lock(&vframe_list_lock); + ret = (vframe_list_tail == NULL) ?0 :1; + pthread_mutex_unlock(&vframe_list_lock); + return ret; +} + +int aframe_have_more(void) +{ + int ret; + pthread_mutex_lock(&aframe_list_lock); + ret = (aframe_list_tail == NULL) ?0 :1; + pthread_mutex_unlock(&aframe_list_lock); + return ret; +} + +/* ------------------------------------------------------------------ */ +/* Frame copying routines */ +/* ------------------------------------------------------------------ */ + +void aframe_copy(aframe_list_t *dst, const aframe_list_t *src, + int copy_data) +{ + if (!dst || !src) { + tc_log_warn(__FILE__, "aframe_copy: given NULL frame pointer"); + return; + } + + /* copy all common fields with just one move */ + ac_memcpy(dst, src, sizeof(frame_list_t)); + + if (copy_data == 1) { + /* really copy video data */ + ac_memcpy(dst->audio_buf, src->audio_buf, dst->audio_size); + } else { + /* soft copy, new frame points to old audio data */ + dst->audio_buf = src->audio_buf; + } +} + +void vframe_copy(vframe_list_t *dst, const vframe_list_t *src, + int copy_data) +{ + if (!dst || !src) { + tc_log_warn(__FILE__, "vframe_copy: given NULL frame pointer"); + return; + } + + /* copy all common fields with just one move */ + ac_memcpy(dst, src, sizeof(frame_list_t)); + + dst->deinter_flag = src->deinter_flag; + dst->free = src->free; + /* + * we assert that plane pointers *are already properly set* + * we're focused on copy _content_ here. + */ + + if (copy_data == 1) { + /* really copy video data */ + ac_memcpy(dst->video_buf, src->video_buf, dst->video_size); + } else { + /* soft copy, new frame points to old video data */ + dst->video_buf = src->video_buf; + } +} + +/*************************************************************************/ + +void vframe_get_counters(int *im, int *fl, int *ex) +{ + pthread_mutex_lock(&vframe_list_lock); + *im = tc_video_ringbuffer.null + tc_video_ringbuffer.empty; + *fl = tc_video_ringbuffer.wait + tc_video_ringbuffer.locked; + *ex = tc_video_ringbuffer.ready; + pthread_mutex_unlock(&vframe_list_lock); +} + +void aframe_get_counters(int *im, int *fl, int *ex) +{ + pthread_mutex_lock(&aframe_list_lock); + *im = tc_audio_ringbuffer.null + tc_audio_ringbuffer.empty; + *fl = tc_audio_ringbuffer.wait + tc_audio_ringbuffer.locked; + *ex = tc_audio_ringbuffer.ready; + pthread_mutex_unlock(&aframe_list_lock); +} + +void tc_framebuffer_get_counters(int *im, int *fl, int *ex) +{ + int v_im, v_fl, v_ex, a_im, a_fl, a_ex; + + vframe_get_counters(&v_im, &v_fl, &v_ex); + aframe_get_counters(&a_im, &a_fl, &a_ex); + + *im = v_im + a_im; + *fl = v_fl + a_fl; + *ex = v_ex + a_ex; +} + +/*************************************************************************/ +/* + * Local variables: + * c-file-style: "stroustrup" + * c-file-offsets: ((case-label . *) (statement-case-intro . *)) + * indent-tabs-mode: nil + * End: + * + * vim: expandtab shiftwidth=4: + */ |
