From: libvidcap c. <lib...@li...> - 2007-11-26 17:59:41
|
Revision: 65 http://libvidcap.svn.sourceforge.net/libvidcap/?rev=65&view=rev Author: bcholew Date: 2007-11-26 09:59:33 -0800 (Mon, 26 Nov 2007) Log Message: ----------- Add a capture timer thread to strictly enforce framerate (but preserve code for use without this thread). Add a double buffer to pass frames between threads. Abstract threading and mutexes for linux, mac and windows. Add new double_buffer and os_funcs files to vcproj and Makefiles. Simplify and rearrange some includes. Modified Paths: -------------- trunk/contrib/win/vs2005/libvidcap.vcproj trunk/src/Makefile.am trunk/src/directshow/DShowSrcManager.cpp trunk/src/directshow/DevMonitor.cpp trunk/src/directshow/DevMonitor.h trunk/src/directshow/DirectShowSource.cpp trunk/src/directshow/SourceStateMachine.cpp trunk/src/sapi.c trunk/src/sapi_context.h trunk/src/sapi_dshow.cpp trunk/src/vidcap.c Added Paths: ----------- trunk/src/double_buffer.c trunk/src/double_buffer.h trunk/src/os_funcs.h Modified: trunk/contrib/win/vs2005/libvidcap.vcproj =================================================================== --- trunk/contrib/win/vs2005/libvidcap.vcproj 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/contrib/win/vs2005/libvidcap.vcproj 2007-11-26 17:59:33 UTC (rev 65) @@ -228,6 +228,10 @@ </FileConfiguration> </File> <File + RelativePath="..\..\..\src\double_buffer.c" + > + </File> + <File RelativePath="..\..\..\src\directshow\DShowSrcManager.cpp" > <FileConfiguration @@ -372,6 +376,10 @@ > </File> <File + RelativePath="..\..\..\src\double_buffer.h" + > + </File> + <File RelativePath="..\..\..\src\directshow\DShowSrcManager.h" > </File> @@ -388,6 +396,10 @@ > </File> <File + RelativePath="..\..\..\src\os_funcs.h" + > + </File> + <File RelativePath="..\..\..\src\sapi.h" > </File> Modified: trunk/src/Makefile.am =================================================================== --- trunk/src/Makefile.am 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/Makefile.am 2007-11-26 17:59:33 UTC (rev 65) @@ -19,10 +19,13 @@ conv_to_rgb.c \ conv_to_i420.c \ conv_to_yuy2.c \ + double_buffer.c \ + double_buffer.h \ hotlist.c \ hotlist.h \ logging.c \ logging.h \ + os_funcs.h \ sapi.c \ sapi.h \ sapi_context.h \ Modified: trunk/src/directshow/DShowSrcManager.cpp =================================================================== --- trunk/src/directshow/DShowSrcManager.cpp 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/directshow/DShowSrcManager.cpp 2007-11-26 17:59:33 UTC (rev 65) @@ -22,7 +22,7 @@ // <http://www.gnu.org/licenses/>. // -#include <windows.h> +#include "os_funcs.h" #include <cstring> #include <cstdio> #include <cwchar> Modified: trunk/src/directshow/DevMonitor.cpp =================================================================== --- trunk/src/directshow/DevMonitor.cpp 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/directshow/DevMonitor.cpp 2007-11-26 17:59:33 UTC (rev 65) @@ -22,11 +22,12 @@ // <http://www.gnu.org/licenses/>. // -#include "DevMonitor.h" +#include "os_funcs.h" #include <dbt.h> #include <process.h> #include <string> #include "sapi_context.h" +#include "DevMonitor.h" #include "logging.h" DevMonitor::DevMonitor() Modified: trunk/src/directshow/DevMonitor.h =================================================================== --- trunk/src/directshow/DevMonitor.h 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/directshow/DevMonitor.h 2007-11-26 17:59:33 UTC (rev 65) @@ -25,21 +25,8 @@ #ifndef _DEVMONITOR_H_ #define _DEVMONITOR_H_ -#include <windows.h> #include <vidcap/vidcap.h> -// Allow use of features specific to Windows XP or later. -#ifndef _WIN32_WINNT - -// Change this to the appropriate value to target other versions of Windows. -// see: http://msdn2.microsoft.com/en-us/library/aa383745.aspx -#define _WIN32_WINNT 0x0501 - -#endif - -// Exclude rarely-used stuff from Windows headers -#define WIN32_LEAN_AND_MEAN - class DevMonitor { Modified: trunk/src/directshow/DirectShowSource.cpp =================================================================== --- trunk/src/directshow/DirectShowSource.cpp 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/directshow/DirectShowSource.cpp 2007-11-26 17:59:33 UTC (rev 65) @@ -22,7 +22,7 @@ // <http://www.gnu.org/licenses/>. // -#include <windows.h> +#include "os_funcs.h" #include <cstring> #include <cstdio> #include <cwchar> @@ -686,6 +686,9 @@ static_cast<double>(fmtNominal->fps_denominator); // check framerate + // TODO: a timer thread could enforce this framerate, + // but would need to pass src_ctx->use_timer_thread here + // to know that if ( fps > fpsMax ) { freeMediaType(*pMediaType); Modified: trunk/src/directshow/SourceStateMachine.cpp =================================================================== --- trunk/src/directshow/SourceStateMachine.cpp 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/directshow/SourceStateMachine.cpp 2007-11-26 17:59:33 UTC (rev 65) @@ -22,11 +22,9 @@ // <http://www.gnu.org/licenses/>. // -#include <windows.h> - +#include "sapi.h" #include "hotlist.h" #include "logging.h" -#include "sapi.h" #include "SourceStateMachine.h" SourceStateMachine::SourceStateMachine(struct sapi_src_context *src, Added: trunk/src/double_buffer.c =================================================================== --- trunk/src/double_buffer.c (rev 0) +++ trunk/src/double_buffer.c 2007-11-26 17:59:33 UTC (rev 65) @@ -0,0 +1,200 @@ +/* + * libvidcap - a cross-platform video capture library + * + * Copyright 2007 Wimba, Inc. + * + * Contributors: + * Peter Grayson <jpg...@gm...> + * Bill Cholewka <bc...@gm...> + * + * libvidcap is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * libvidcap 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <stdlib.h> +#include <string.h> + +#include "double_buffer.h" + +#include "logging.h" + +/* The write thread inserts (and frees) objects. + * The read thread reads (copies) objects + * + * It is the responsibility of the read thread's 'application' to + * free the copied objects. + * + * If the read thread 'application' blocks for too long (doesn't + * read often enough), the write thread will free a buffered object + * to make room for an incoming object + */ + +/* NOTE: This function is passed function pointers to call when + * necessary to free or copy an object + */ +struct double_buffer * +double_buffer_create( void (*free_me)(void *), void * (*copy_me)(void *) ) +{ + + struct double_buffer * db_buff; + + if ( !free_me || !copy_me ) + return 0; + + db_buff = calloc(1, sizeof(*db_buff)); + + if ( !db_buff ) + { + log_oom(__FILE__, __LINE__); + return 0; + } + + db_buff->read_count = 0; + db_buff->write_count = 0; + db_buff->count[0] = -1; + db_buff->count[1] = -1; + db_buff->objects[0] = 0; + db_buff->objects[1] = 0; + + db_buff->free_object = free_me; + db_buff->copy_object = copy_me; + + vc_mutex_init(&db_buff->locks[0]); + vc_mutex_init(&db_buff->locks[1]); + + /* TODO: remove this (debug) counter */ + db_buff->num_insert_too_far_failures = 0; + + return db_buff; +} + +void +double_buffer_destroy(struct double_buffer * db_buff) +{ + vc_mutex_destroy(&db_buff->locks[1]); + vc_mutex_destroy(&db_buff->locks[0]); + + if ( db_buff->write_count > 0 ) + db_buff->free_object(db_buff->objects[0]); + + if ( db_buff->write_count > 1 ) + db_buff->free_object(db_buff->objects[1]); + + free(db_buff); +} + +void +double_buffer_insert(struct double_buffer * db_buff, void * new_object) +{ + const int insertion_index = db_buff->write_count % 2; + + /* TODO: we could eliminate a copy by having the reader free + * all buffered objects. This would require that a failed + * check below result in the object being freed. This + * would ensure that the reader sees all BUFFERED objects. + * + * The reader would then need to take care to free objects + * as they become outdated. + * + * The tradeoff is the occasional dropped object when + * the writer gets ahead a little. The counter can help + * to evaluate the cost of this tradeoff + */ + /* don't get far ahead of the reader*/ + if ( db_buff->write_count > ( db_buff->read_count + 2 ) ) + { + db_buff->num_insert_too_far_failures++; + /* return; */ + } + + /* get exclusive access to the correct buffer */ + if ( vc_mutex_trylock(&db_buff->locks[insertion_index] )) + { + /* failed to obtain lock */ + /* drop incoming object */ + db_buff->free_object(new_object); + return; + } + + /* free the slot object if something is already there */ + if ( db_buff->write_count > 1 ) + db_buff->free_object(db_buff->objects[insertion_index]); + + /* insert object */ + db_buff->objects[insertion_index] = new_object; + + /* stamp the buffer with the write count */ + db_buff->count[insertion_index] = db_buff->write_count; + + /* advance the write count */ + ++db_buff->write_count; + + /* unlock the correct lock */ + vc_mutex_unlock(&db_buff->locks[insertion_index]); +} + +void * +double_buffer_read(struct double_buffer * db_buff) +{ + int copy_index = 1; + void * buff; + + if ( db_buff->write_count < 1 ) + return 0; + + /* NOTE: Try the newest buffer first + * IF that lock fails THEN try other + */ + if ( db_buff->count[0] > db_buff->count[1] ) + copy_index = 0; + + if ( vc_mutex_trylock(&db_buff->locks[copy_index] ) ) + { + /* failed to obtain lock */ + /* try the other (older) slot? */ + if ( db_buff->write_count < 2 ) + return 0; + + copy_index = 1 - copy_index; + + /* Failed to obtain other lock? */ + /* This should be pretty rare */ + if ( vc_mutex_trylock(&db_buff->locks[copy_index] ) ) + return 0; + } + + /* Is this buffer older than the last-read? + * TODO: could this happen? + */ + if ( db_buff->count[copy_index] < db_buff->read_count ) + { + vc_mutex_unlock(&db_buff->locks[copy_index]); + return 0; + } + + buff = db_buff->copy_object(db_buff->objects[copy_index]); + + db_buff->read_count = db_buff->count[copy_index]; + + vc_mutex_unlock(&db_buff->locks[copy_index]); + + return buff; +} + +int +double_buffer_count(struct double_buffer * db_buff) +{ + return db_buff->write_count; +} Property changes on: trunk/src/double_buffer.c ___________________________________________________________________ Name: svn:executable + * Added: trunk/src/double_buffer.h =================================================================== --- trunk/src/double_buffer.h (rev 0) +++ trunk/src/double_buffer.h 2007-11-26 17:59:33 UTC (rev 65) @@ -0,0 +1,65 @@ +/* + * libvidcap - a cross-platform video capture library + * + * Copyright 2007 Wimba, Inc. + * + * Contributors: + * Peter Grayson <jpg...@gm...> + * Bill Cholewka <bc...@gm...> + * + * libvidcap is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * libvidcap 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _DOUBLE_BUFFER_H +#define _DOUBLE_BUFFER_H + +#include "os_funcs.h" + +#define _DOUBLE_MEANS_TWO_ 2 + +struct double_buffer +{ + int read_count; + int write_count; + void * objects[_DOUBLE_MEANS_TWO_]; + vc_mutex locks[_DOUBLE_MEANS_TWO_]; + + int count[_DOUBLE_MEANS_TWO_]; + + void * (*copy_object)(void *); + void (*free_object)(void *); + + /* TODO: remove */ + int num_insert_too_far_failures; +}; + +struct double_buffer * +double_buffer_create( void (*free_obj_func)(void *), + void * (*copy_object)(void *)); + +void +double_buffer_destroy(struct double_buffer * db_buff); + +void +double_buffer_insert(struct double_buffer * db_buff, void * object); + +void * +double_buffer_read(struct double_buffer * db_buff); + +int +double_buffer_count(struct double_buffer * db_buff); + +#endif Property changes on: trunk/src/double_buffer.h ___________________________________________________________________ Name: svn:executable + * Added: trunk/src/os_funcs.h =================================================================== --- trunk/src/os_funcs.h (rev 0) +++ trunk/src/os_funcs.h 2007-11-26 17:59:33 UTC (rev 65) @@ -0,0 +1,219 @@ +/* + * libvidcap - a cross-platform video capture library + * + * Copyright 2007 Wimba, Inc. + * + * Contributors: + * Peter Grayson <jpg...@gm...> + * Bill Cholewka <bc...@gm...> + * + * libvidcap is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * libvidcap 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#ifndef _OS_FUNCS_H +#define _OS_FUNCS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(HAVE_NANOSLEEP) || defined(HAVE_GETTIMEOFDAY) +#include <sys/time.h> +#include <time.h> +#endif + +/* os-dependent macros */ +#if defined(WIN32) || defined(_WIN32_WCE) + +#ifndef _WIN32_WINNT +// Change this to the appropriate value to target other versions of Windows. +// see: http://msdn2.microsoft.com/en-us/library/aa383745.aspx +#define _WIN32_WINNT 0x0500 +#endif + +#include <windows.h> + +#if !defined(_WIN32_WCE) +#include <process.h> +#endif + +#define STDCALL __stdcall +typedef CRITICAL_SECTION vc_mutex; +typedef uintptr_t vc_thread; + +#else + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <pthread.h> + +#define STDCALL +typedef pthread_mutex_t vc_mutex; +typedef pthread_t vc_thread; + +#endif + +#ifdef MACOSX +#include <mach/mach_init.h> +#include <mach/thread_policy.h> +#include <sched.h> +#include <sys/sysctl.h> +#endif + +#ifndef HAVE_GETTIMEOFDAY +static __inline int +gettimeofday(struct timeval * tv, struct timezone * tz) +#ifdef WIN32 +{ + FILETIME ft; + LARGE_INTEGER li; + __int64 t; + static int tzflag; + const __int64 EPOCHFILETIME = 116444736000000000i64; + if ( tv ) + { + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + t = li.QuadPart; /* In 100-nanosecond intervals */ + t -= EPOCHFILETIME; /* Offset to the Epoch time */ + t /= 10; /* In microseconds */ + tv->tv_sec = (long)(t / 1000000); + tv->tv_usec = (long)(t % 1000000); + } + + return 0; + +#else /* !defined(_WINDOWS) */ + errno = ENOSYS; + return -1; +#endif +} +#endif /* !defined(HAVE_GETTIMEOFDAY) */ + + +static __inline int +vc_create_thread(vc_thread *thread, + unsigned int (STDCALL * thread_func)(void *), + void *args, unsigned int *thread_id) +{ +#ifdef WIN32 + *thread = (uintptr_t)_beginthreadex(NULL, 0, thread_func, + (void *)args, 0, thread_id); + + /* FIXME: log the error */ + if ( thread == 0 ) + return -1; +#else + int ret; + void * (*func)(void *) = (void * (*)(void *))thread_func; + + ret = pthread_create(thread, NULL, func, args); + + /* FIXME: log the error */ + if ( ret ) + return -1; +#endif + + return 0; +} + +static __inline void +vc_thread_join(vc_thread *thread) +{ +#ifdef WIN32 + HANDLE _thread = (HANDLE)thread; + + WaitForSingleObject(_thread, INFINITE); +#else + pthread_t *_thread = (pthread_t *)thread; + + pthread_join(*_thread, 0); +#endif +} + +static __inline void +vc_mutex_init(vc_mutex *m) +{ +#ifdef WIN32 + InitializeCriticalSection(m); +#else + pthread_mutex_init(m, NULL); +#endif +} + +static __inline void +vc_mutex_lock(vc_mutex *m) +{ +#ifdef WIN32 + EnterCriticalSection(m); +#else + pthread_mutex_lock(m); +#endif +} + +static __inline int +vc_mutex_trylock(vc_mutex *m) +{ +#ifdef WIN32 + return !TryEnterCriticalSection(m); +#else + return pthread_mutex_trylock(m); +#endif +} + +static __inline void +vc_mutex_unlock(vc_mutex *m) +{ +#ifdef WIN32 + LeaveCriticalSection(m); +#else + pthread_mutex_unlock(m); +#endif +} + +static __inline void +vc_mutex_destroy(vc_mutex *m) +{ +#ifdef WIN32 + DeleteCriticalSection(m); +#else + pthread_mutex_destroy(m); +#endif +} + +static __inline void +vc_millisleep(long ms) +{ +#ifdef WIN32 + + Sleep(ms); + +#else + + struct timespec req; + + req.tv_nsec = (ms % 1000) * 1000000; + req.tv_sec = ms / 1000; + + nanosleep(&req, NULL); + +#endif +} + +#endif Property changes on: trunk/src/os_funcs.h ___________________________________________________________________ Name: svn:executable + * Modified: trunk/src/sapi.c =================================================================== --- trunk/src/sapi.c 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/sapi.c 2007-11-26 17:59:33 UTC (rev 65) @@ -30,36 +30,9 @@ #include "logging.h" #include "sapi.h" -#ifndef HAVE_GETTIMEOFDAY -static __inline int gettimeofday(struct timeval * tv, struct timezone * tz) -#ifdef WIN32 -{ - FILETIME ft; - LARGE_INTEGER li; - __int64 t; - static int tzflag; - const __int64 EPOCHFILETIME = 116444736000000000i64; - if ( tv ) - { - GetSystemTimeAsFileTime(&ft); - li.LowPart = ft.dwLowDateTime; - li.HighPart = ft.dwHighDateTime; - t = li.QuadPart; /* In 100-nanosecond intervals */ - t -= EPOCHFILETIME; /* Offset to the Epoch time */ - t /= 10; /* In microseconds */ - tv->tv_sec = (long)(t / 1000000); - tv->tv_usec = (long)(t % 1000000); - } +static int +deliver_frame(struct sapi_src_context * src_ctx); - return 0; - -#else /* !defined(_WINDOWS) */ - errno = ENOSYS; - return -1; -#endif -} -#endif - static __inline int tv_greater_or_equal(struct timeval * t1, struct timeval * t0) { @@ -76,7 +49,7 @@ } static __inline void -tv_add_usecs(struct timeval *t1, struct timeval *t0, int usecs) +tv_add_usecs(struct timeval *t1, struct timeval *t0, long usecs) { int secs_carried = 0; @@ -216,6 +189,19 @@ return 0; } +static void +wait_for_error_ack(struct sapi_src_context * src_ctx) +{ + while ( !src_ctx->capture_error_ack ) + vc_millisleep(10); +} + +static void +acknowledge_error(struct sapi_src_context * src_ctx) +{ + src_ctx->capture_error_ack = 1; +} + /* NOTE: stride-ignorant sapis should pass a stride of zero */ int sapi_src_capture_notify(struct sapi_src_context * src_ctx, @@ -223,25 +209,121 @@ int stride, int error_status) { - struct vidcap_capture_info cap_info; - /* NOTE: We may be called here by the capture thread while the * main thread is clearing capture_data and capture_callback * from within vidcap_src_capture_stop(). */ + + /* Package the video information */ + struct frame_info * frame = malloc(sizeof(*frame)); + if ( !frame ) + return -1; + + frame->cap_info = malloc(sizeof(struct vidcap_capture_info)); + if ( !frame->cap_info ) + { + free(frame); + return -1; + } + + frame->cap_info->video_data_size = video_data_size; + frame->cap_info->error_status = error_status; + frame->stride = stride; + + if ( src_ctx->use_timer_thread ) + { + /* Copy this buffer. It might not exist long enough for + * read thread (capture timer thread) to copy it + */ + frame->cap_info->video_data = malloc(video_data_size); + if ( !frame->cap_info->video_data ) + { + free(frame->cap_info); + free(frame); + return -1; + } + + memcpy(frame->cap_info->video_data, video_data, video_data_size); + + /* buffer the frame - for processing by the timer thread */ + double_buffer_insert(src_ctx->double_buff, frame); + + /* If there's an error, wait here until it's acknowledged + * by the timer thread (after having delivered it to the app). + */ + if ( error_status ) + wait_for_error_ack(src_ctx); + } + else + { + frame->cap_info->video_data = video_data; + + /* process the frame now */ + src_ctx->no_timer_thread_frame = frame; + deliver_frame(src_ctx); + } + + if ( error_status ) + { + src_ctx->src_state = src_bound; + src_ctx->capture_callback = 0; + src_ctx->capture_data = VIDCAP_INVALID_USER_DATA; + } + + return 0; +} + +static int +deliver_frame(struct sapi_src_context * src_ctx) +{ vidcap_src_capture_callback cap_callback = src_ctx->capture_callback; void * cap_data = src_ctx->capture_data; void * buf = 0; int buf_data_size = 0; - int send_frame = 0; - if ( !error_status ) - send_frame = enforce_framerate(src_ctx); + struct vidcap_capture_info cap_info; - if ( send_frame < 0 ) - error_status = -1000; + struct frame_info * frame; + const char * video_data; + int video_data_size; + int stride; + int error_status; + if ( src_ctx->use_timer_thread ) + { + frame = (struct frame_info *) + double_buffer_read(src_ctx->double_buff); + + if ( !frame ) + return -1; + } + else + { + frame = src_ctx->no_timer_thread_frame; + } + + video_data = frame->cap_info->video_data; + video_data_size = frame->cap_info->video_data_size; + stride = frame->stride; + error_status = frame->cap_info->error_status; + + /* FIXME: right now enforce_framerate() and the capture timer + * thread's main loop BOTH use the frame_time_next field + */ + if ( src_ctx->use_timer_thread ) + { + send_frame = 1; + } + else + { + if ( !error_status ) + send_frame = enforce_framerate(src_ctx); + + if ( send_frame < 0 ) + error_status = -1000; + } + cap_info.error_status = error_status; if ( !cap_info.error_status && stride && @@ -290,22 +372,143 @@ if ( ( send_frame || error_status ) && cap_callback && cap_data != VIDCAP_INVALID_USER_DATA ) { - /* FIXME: Need to check return code. - * Application may want capture to stop + /* FIXME: Need to check return code (and pass it back). + * Application may want capture to stop. + * Ensure we don't perform any more callbacks. */ cap_callback(src_ctx, cap_data, &cap_info); - if ( cap_info.error_status ) + if ( src_ctx->use_timer_thread ) { - src_ctx->src_state = src_bound; - src_ctx->capture_callback = 0; - src_ctx->capture_data = VIDCAP_INVALID_USER_DATA; + /* delete frame */ + free(frame->cap_info->video_data); + free(frame->cap_info); + free(frame); + + if ( error_status ) + { + /* Let capture thread know that the app + * Has received the error status. + * Ensure we don't deliver any more frames. + */ + acknowledge_error(src_ctx); + + /* inform calling function of error */ + return 1; + } } } return 0; } +unsigned int +STDCALL sapi_src_timer_thread_func(void *args) +{ + struct sapi_src_context * src_ctx = args; + struct timeval tv_now; + + /* change to faster rate at capture time, and reduce when capture stopped */ + const long idle_state_sleep_period_ms = 100; + long sleep_ms = idle_state_sleep_period_ms; + int first_time = 1; + const int sleeps_per_capture = 1; + int got_frame = 0; + int ret; + int capture_error = 0; /* FIXME: perhaps should exit on error */ + + src_ctx->timer_thread_idle = 1; + + if ( gettimeofday(&tv_now, 0) ) + { + src_ctx->capture_timer_thread_started = -1; + log_error("gettimeofday not supported\n"); + return -1; + } + + src_ctx->frame_time_next.tv_sec = tv_now.tv_sec; + src_ctx->frame_time_next.tv_usec = tv_now.tv_usec; + + log_info("capture timer thread now running\n"); + src_ctx->capture_timer_thread_started = 1; + + while ( !src_ctx->kill_timer_thread ) + { + gettimeofday(&tv_now, 0); + + /* time to attempt to read a frame? */ + if ( capture_error || src_ctx->src_state != src_capturing || + !tv_greater_or_equal(&tv_now, &src_ctx->frame_time_next) ) + { + if ( src_ctx->src_state != src_capturing ) + { + sleep_ms = idle_state_sleep_period_ms; + first_time = 1; + } + + vc_millisleep(sleep_ms); + } + else + { + src_ctx->timer_thread_idle = 0; + /* FIXME: memory barrier needed? */ + + /* attempt to read and deliver a frame */ + ret = deliver_frame(src_ctx); + + got_frame = !ret; + capture_error = ret > 0; + + /* Is this the first frame? */ + if ( got_frame && first_time ) + { + first_time = 0; + + /* re-initialize when next to check for a frame */ + src_ctx->frame_time_next.tv_sec = tv_now.tv_sec; + src_ctx->frame_time_next.tv_usec = tv_now.tv_usec; + + sleep_ms = (1000 / sleeps_per_capture) * + src_ctx->fmt_nominal.fps_denominator / + src_ctx->fmt_nominal.fps_numerator; + } + + if ( !first_time ) + { + /* update when next to check for a frame */ + /* FIXME: reduce round-off */ + tv_add_usecs(&src_ctx->frame_time_next, &src_ctx->frame_time_next, + 1000000 * + src_ctx->fmt_nominal.fps_denominator / + src_ctx->fmt_nominal.fps_numerator); + } + else + { + /* still no first frame */ + /* update when next to check for a frame */ + tv_add_usecs(&src_ctx->frame_time_next, &tv_now, + 1000000 * + src_ctx->fmt_nominal.fps_denominator / + src_ctx->fmt_nominal.fps_numerator); + } + } + + /* FIXME: memory barrier needed? */ + src_ctx->timer_thread_idle = 1; + } + + log_info("capture timer thread now exiting...\n"); + + return 0; +} + +void +sapi_src_timer_thread_idled(struct sapi_src_context * src_ctx) +{ + while ( !src_ctx->timer_thread_idle ) + vc_millisleep(10); +} + int sapi_can_convert_native_to_nominal(const struct vidcap_fmt_info * fmt_native, const struct vidcap_fmt_info * fmt_nominal) @@ -333,3 +536,4 @@ return 0; } + Modified: trunk/src/sapi_context.h =================================================================== --- trunk/src/sapi_context.h 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/sapi_context.h 2007-11-26 17:59:33 UTC (rev 65) @@ -26,26 +26,20 @@ #ifndef _SAPI_CONTEXT_H #define _SAPI_CONTEXT_H -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef WIN32 -#include <windows.h> -#endif - -#if defined(HAVE_NANOSLEEP) || defined(HAVE_GETTIMEOFDAY) -#include <sys/time.h> -#include <time.h> -#endif - #include <vidcap/vidcap.h> +#include "double_buffer.h" #include "sliding_window.h" #include "conv.h" struct sapi_context; +struct frame_info +{ + struct vidcap_capture_info *cap_info; + int stride; +}; + struct sapi_src_context { int (*release)(struct sapi_src_context *); @@ -64,6 +58,7 @@ struct vidcap_src_info src_info; struct sliding_window * frame_times; struct timeval frame_time_next; + struct double_buffer * double_buff; struct vidcap_fmt_info fmt_nominal; struct vidcap_fmt_info fmt_native; @@ -82,6 +77,15 @@ vidcap_src_capture_callback capture_callback; void * capture_data; + int use_timer_thread; + struct frame_info * no_timer_thread_frame; + vc_thread capture_timer_thread; + unsigned int capture_timer_thread_id; + int capture_timer_thread_started; + int timer_thread_idle; + int kill_timer_thread; + int capture_error_ack; + void * priv; }; @@ -124,4 +128,10 @@ void sapi_src_fps_info_clean(struct sapi_src_context *); +void +sapi_src_timer_thread_idled(struct sapi_src_context * src_ctx); + +unsigned int +STDCALL sapi_src_timer_thread_func(void *); + #endif Modified: trunk/src/sapi_dshow.cpp =================================================================== --- trunk/src/sapi_dshow.cpp 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/sapi_dshow.cpp 2007-11-26 17:59:33 UTC (rev 65) @@ -22,14 +22,12 @@ // <http://www.gnu.org/licenses/>. // -#include <windows.h> #include <stdio.h> +#include "sapi.h" #include "SourceStateMachine.h" #include "DShowSrcManager.h" - #include "logging.h" -#include "sapi.h" static const char * identifier = "DirectShow"; static const char * description = "DirectShow video capture API"; @@ -177,7 +175,7 @@ return -1; } - if ( src_ctx->priv == 0 ) + if ( src_ctx->priv == (void *)0 ) { log_warn("failed constructing source state machine for '%s'\n", src_info->identifier); Modified: trunk/src/vidcap.c =================================================================== --- trunk/src/vidcap.c 2007-11-20 17:51:57 UTC (rev 64) +++ trunk/src/vidcap.c 2007-11-26 17:59:33 UTC (rev 65) @@ -26,12 +26,6 @@ #include <stdlib.h> #include <string.h> -#include <vidcap/vidcap.h> - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - #include "logging.h" #include "sapi_context.h" #include "sapi.h" @@ -312,6 +306,41 @@ return 0; } + /* TODO: right now we always use the timer thread to enforce + * a very accurate framerate. Alternatively, we could + * allow an application to choose to forego this feature + * and the associated overhead + */ + src_ctx->use_timer_thread = 1; + + if ( src_ctx->use_timer_thread ) + { + src_ctx->kill_timer_thread = 0; + src_ctx->capture_timer_thread_started = 0; + src_ctx->capture_timer_thread = 0; + /* FIXME: memory barrier needed? */ + + if ( vc_create_thread(&src_ctx->capture_timer_thread, + sapi_src_timer_thread_func, src_ctx, + &src_ctx->capture_timer_thread_id) ) + { + src_ctx->capture_timer_thread = 0; + goto bail; + } + + /* ensure thread has started before proceeding */ + while ( !src_ctx->capture_timer_thread_started ) + vc_millisleep(10); + + if ( src_ctx->capture_timer_thread_started < 0 ) + { + log_error("source timer thread failed to start.\n"); + goto bail; + } + + log_info("using a capture timer thread for this source\n"); + } + if ( sapi_ctx->acquire_source(sapi_ctx, src_ctx, src_info) ) { log_error("failed to acquire %s\n", src_info ? @@ -330,6 +359,15 @@ return src_ctx; bail: + /* wait for it to exit (if it exists) */ + if ( src_ctx->use_timer_thread && src_ctx->capture_timer_thread ) + { + /* signal to timer thread to stop */ + src_ctx->kill_timer_thread = 1; + + vc_thread_join(&src_ctx->capture_timer_thread); + } + free(src_ctx); return 0; } @@ -352,6 +390,15 @@ if ( src_ctx->src_state == src_capturing ) return -1; + if ( src_ctx->use_timer_thread && src_ctx->capture_timer_thread ) + { + /* signal timer thread to exit */ + src_ctx->kill_timer_thread = 1; + + /* wait for it to exit */ + vc_thread_join(&src_ctx->capture_timer_thread); + } + ret = src_ctx->release(src_ctx); if ( src_ctx->fmt_list_len ) @@ -499,6 +546,53 @@ return 0; } +void +free_frame_info(void * fr) +{ + struct frame_info *frame = (struct frame_info *)fr; + + /* FIXME: unconstify video_data */ + free(frame->cap_info->video_data); + free(frame->cap_info); + free(frame); +} + +void * +copy_frame_info(void * fr) +{ + struct frame_info *frame = (struct frame_info *)fr; + + struct frame_info *dup_frame = malloc(sizeof(*dup_frame)); + + if ( !dup_frame ) + return 0; + + dup_frame->cap_info = calloc(1, sizeof(*dup_frame->cap_info)); + if ( !dup_frame->cap_info ) + { + free(dup_frame); + return 0; + } + + dup_frame->stride = frame->stride; + dup_frame->cap_info->error_status = frame->cap_info->error_status; + dup_frame->cap_info->video_data_size = frame->cap_info->video_data_size; + dup_frame->cap_info->video_data = malloc(frame->cap_info->video_data_size); + if ( !dup_frame->cap_info->video_data ) + { + free(dup_frame->cap_info); + free(dup_frame); + return 0; + } + + /* FIXME: unconstify cap_info->video_data */ + memcpy(dup_frame->cap_info->video_data, + frame->cap_info->video_data, + frame->cap_info->video_data_size); + + return dup_frame; +} + int vidcap_src_capture_start(vidcap_src * src, vidcap_src_capture_callback callback, @@ -524,6 +618,16 @@ if ( !src_ctx->frame_times ) return -2; + src_ctx->double_buff = double_buffer_create(&free_frame_info, + ©_frame_info); + + if ( !src_ctx->double_buff ) + { + sliding_window_destroy(src_ctx->frame_times); + src_ctx->frame_times = 0; + return -5; + } + src_ctx->capture_callback = callback; src_ctx->capture_data = user_data; @@ -531,6 +635,10 @@ { src_ctx->capture_callback = 0; src_ctx->capture_data = VIDCAP_INVALID_USER_DATA; + + double_buffer_destroy(src_ctx->double_buff); + src_ctx->double_buff = 0; + sliding_window_destroy(src_ctx->frame_times); src_ctx->frame_times = 0; return ret; @@ -551,12 +659,23 @@ ret = src_ctx->stop_capture(src_ctx); + /* signal to timer thread that capture has stopped */ + src_ctx->src_state = src_bound; + + if ( src_ctx->use_timer_thread ) + { + /* FIXME: memory barrier needed? */ + sapi_src_timer_thread_idled(src_ctx); + } + + double_buffer_destroy(src_ctx->double_buff); + src_ctx->double_buff = 0; + sliding_window_destroy(src_ctx->frame_times); + src_ctx->frame_times = 0; - src_ctx->frame_times = 0; src_ctx->capture_callback = 0; src_ctx->capture_data = VIDCAP_INVALID_USER_DATA; - src_ctx->src_state = src_bound; return ret; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |