diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2020-07-11 16:47:00 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2020-07-11 16:47:00 -0700 |
commit | 3625160acc1715fc380f58ec3c4248485bed2370 (patch) | |
tree | dc95a32d81daac298cef69879a639029797fb762 /src | |
parent | cfcca8681b88a171fb2cdbb83daa5f22bbedb6b8 (diff) |
*: drop {gtk,qt}-recordmydesktop subdirs
This restores the recordmydesktop/ subdir as root from the mirror I
cloned by fork from.
I have no particular interest in the gtk/qt frontends and it doesn't
appear they were part of a single tree in the past. But I will
probably preserve backwards compatibility of the cli so they can
continue to work with this fork installed.
Diffstat (limited to 'src')
78 files changed, 10311 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..a63c3c4 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,94 @@ +bin_PROGRAMS = recordmydesktop + +recordmydesktop_SOURCES = \ + rmd.c \ + rmd_cache.c \ + rmd_cache.h \ + rmd_cache_audio.c \ + rmd_cache_audio.h \ + rmd_cache_frame.c \ + rmd_cache_frame.h \ + rmd_capture_sound.c \ + rmd_capture_sound.h \ + rmd_encode_cache.c \ + rmd_encode_cache.h \ + rmd_encode_image_buffer.c \ + rmd_encode_image_buffer.h \ + rmd_encode_sound_buffer.c \ + rmd_encode_sound_buffer.h \ + rmd_error.c \ + rmd_error.h \ + rmd_flush_to_ogg.c \ + rmd_flush_to_ogg.h \ + rmd_frame.c \ + rmd_frame.h \ + rmd_get_frame.c \ + rmd_get_frame.h \ + rmd_getzpixmap.c \ + rmd_getzpixmap.h \ + rmd_init_encoder.c \ + rmd_init_encoder.h \ + rmd_initialize_data.c \ + rmd_initialize_data.h \ + rmd_jack.c \ + rmd_jack.h \ + rmd_load_cache.c \ + rmd_load_cache.h \ + rmd_macro.h \ + rmd_make_dummy_pointer.c \ + rmd_make_dummy_pointer.h \ + rmd_math.c \ + rmd_math.h \ + rmd_opendev.c \ + rmd_opendev.h \ + rmd_parseargs.c \ + rmd_parseargs.h \ + rmd_poll_events.c \ + rmd_poll_events.h \ + rmd_queryextensions.c \ + rmd_queryextensions.h \ + rmd_rectinsert.c \ + rmd_rectinsert.h \ + rmd_register_callbacks.c \ + rmd_register_callbacks.h \ + rmd_rescue.c \ + rmd_rescue.h \ + rmd_setbrwindow.c \ + rmd_setbrwindow.h \ + rmd_shortcuts.c \ + rmd_shortcuts.h \ + rmd_specsfile.c \ + rmd_specsfile.h \ + rmd_threads.c \ + rmd_threads.h \ + rmd_timer.c \ + rmd_timer.h \ + rmd_types.h \ + rmd_update_image.c \ + rmd_update_image.h \ + rmd_wm_check.c \ + rmd_wm_check.h \ + rmd_wm_is_compositing.c \ + rmd_wm_is_compositing.h \ + rmd_yuv_utils.c \ + rmd_yuv_utils.h \ + skeleton.c \ + skeleton.h + +recordmydesktop_CPPFLAGS = -D_THREAD_SAFE -pthread -Wall +recordmydesktop_LDFLAGS = @X_LIBS@ @X_EXTRA_LIBS@ @X_PRE_LIBS@ + + +# RectInsert test +TESTS = test-rectinsert +EXTRA_PROGRAMS = test-rectinsert +CLEANFILES = $(EXTRA_PROGRAMS) + +test_rectinsert_SOURCES = \ + test-rectinsert.c \ + test-rectinsert-data.c \ + test-rectinsert-data.h \ + test-rectinsert-types.h \ + rmd_rectinsert.c + +test_rectinsert_CFLAGS = -Wall diff --git a/src/rmd.c b/src/rmd.c new file mode 100644 index 0000000..5a93e79 --- /dev/null +++ b/src/rmd.c @@ -0,0 +1,184 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" + +#include "rmd_cache.h" +#include "rmd_encode_cache.h" +#include "rmd_error.h" +#include "rmd_initialize_data.h" +#include "rmd_parseargs.h" +#include "rmd_queryextensions.h" +#include "rmd_rescue.h" +#include "rmd_setbrwindow.h" +#include "rmd_shortcuts.h" +#include "rmd_threads.h" +#include "rmd_wm_is_compositing.h" +#include "rmd_types.h" + +#include <X11/Xlib.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +int main(int argc, char **argv){ + ProgData pdata = {}; + EncData enc_data = {}; + CacheData cache_data = {}; + int exit_status = 0; + +#ifdef HAVE_LIBJACK + JackData jdata; + + // Give jack access to program data, mainly for program state + jdata.pdata = &pdata; + pdata.jdata = &jdata; +#endif + + rmdSetupDefaultArgs(&pdata.args); + + if (!rmdParseArgs(argc, argv, &pdata.args)) + exit(1); + + if (pdata.args.rescue_path != NULL) + exit(rmdRescue(pdata.args.rescue_path)); + + if (XInitThreads() == 0) { + fprintf(stderr, "Couldn't initialize thread support!\n"); + exit(7); + } + + if (pdata.args.display != NULL) { + pdata.dpy = XOpenDisplay(pdata.args.display); + XSetErrorHandler(rmdErrorHandler); + } else { + fprintf(stderr, "No display specified for connection!\n"); + exit(8); + } + + if (pdata.dpy == NULL) { + fprintf(stderr, "Cannot connect to X server %s\n", pdata.args.display); + exit(9); + } + + // Query display specs + pdata.specs.screen = DefaultScreen(pdata.dpy); + pdata.specs.width = DisplayWidth(pdata.dpy, pdata.specs.screen); + pdata.specs.height = DisplayHeight(pdata.dpy, pdata.specs.screen); + pdata.specs.root = RootWindow(pdata.dpy, pdata.specs.screen); + pdata.specs.visual = DefaultVisual(pdata.dpy, pdata.specs.screen); + pdata.specs.gc = DefaultGC(pdata.dpy, pdata.specs.screen); + pdata.specs.depth = DefaultDepth(pdata.dpy, pdata.specs.screen); + + if (pdata.specs.depth != 32 && pdata.specs.depth != 24 && pdata.specs.depth != 16) { + fprintf(stderr, + "Only 32bpp, 24bpp, and 16bpp" + " color depth modes are currently supported.\n"); + exit(10); + } + + if (!rmdSetBRWindow(pdata.dpy, &pdata.brwin, &pdata.specs, &pdata.args)) + exit(11); + + if (!pdata.args.nowmcheck && rmdWMIsCompositing( pdata.dpy, pdata.specs.screen ) ) { + + fprintf(stderr, "\nDetected compositing window manager.\n" + "Reverting to full screen capture at every frame.\n" + "To disable this check run with --no-wm-check\n" + "(though that is not advised, since it will " + "probably produce faulty results).\n\n"); + pdata.args.full_shots = 1; + pdata.args.noshared = 0; + } + + rmdQueryExtensions( pdata.dpy, + &pdata.args, + &pdata.damage_event, + &pdata.damage_error, + &pdata.shm_opcode); + + exit_status = rmdInitializeData(&pdata, &enc_data, &cache_data); + if (exit_status) + exit(exit_status); + + if ( !strcmp(pdata.args.pause_shortcut, pdata.args.stop_shortcut) || + rmdRegisterShortcut( pdata.dpy, pdata.specs.root, + pdata.args.pause_shortcut, + &(pdata.pause_key)) || + rmdRegisterShortcut( pdata.dpy, + pdata.specs.root, + pdata.args.stop_shortcut, + &(pdata.stop_key))) { + + fprintf(stderr, "Invalid shortcut, or shortcuts are the same!\n\nUsing defaults.\n"); + + rmdRegisterShortcut( pdata.dpy, + pdata.specs.root, + "Control+Mod1+p", + &(pdata.pause_key)); + + rmdRegisterShortcut( pdata.dpy, + pdata.specs.root, + "Control+Mod1+s", + &(pdata.stop_key)); + } + + //this is where the capturing happens. + rmdThreads(&pdata); + + XCloseDisplay(pdata.dpy); + fprintf(stderr, ".\n"); + + //encode and then cleanup cache + if (!pdata.args.encOnTheFly && !pdata.args.no_encode) { + if (!pdata.aborted) + rmdEncodeCache(&pdata); + + fprintf(stderr, "Cleanning up cache...\n"); + if (rmdPurgeCache(pdata.cache_data, !pdata.args.nosound)) + fprintf(stderr, "Some error occured while cleaning up cache!\n"); + + fprintf(stderr, "Done!!!\n"); + } + + if (pdata.aborted && pdata.args.encOnTheFly) { + if (remove(pdata.args.filename)) { + perror("Error while removing file:\n"); + return 1; + } else { + fprintf(stderr, "SIGABRT received, file %s removed\n", + pdata.args.filename); + return 0; + } + } else + fprintf(stderr, "Goodbye!\n"); + + rmdCleanUp(); + + return exit_status; +} diff --git a/src/rmd_cache.c b/src/rmd_cache.c new file mode 100644 index 0000000..1353bc2 --- /dev/null +++ b/src/rmd_cache.c @@ -0,0 +1,218 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_cache.h" + +#include "rmd_specsfile.h" +#include "rmd_types.h" + +#include <sys/types.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + + +/** +*Construct an number postfixed name +* +* \param name base name +* +* \param newname modified name +* +* \n number to be used as a postfix +* +*/ +static void rmdCacheFileN(char *name, char **newname, int n) { // Nth cache file + char numbuf[8]; + + strcpy(*newname, name); + strcat(*newname, "."); + snprintf(numbuf, 8, "%d", n); + strcat(*newname, numbuf); +} + +int rmdSwapCacheFilesWrite(char *name, int n, gzFile *fp, FILE **ucfp) { + char *newname = malloc(strlen(name) + 10); + + rmdCacheFileN(name, &newname, n); + if (*fp == NULL) { + fflush(*ucfp); + fclose(*ucfp); + *ucfp = fopen(newname, "wb"); + } else { + gzflush(*fp, Z_FINISH); + gzclose(*fp); + *fp = gzopen(newname, "wb0f"); + } + free(newname); + + return ((*fp == NULL) && (*ucfp == NULL)); +} + +int rmdSwapCacheFilesRead(char *name, int n, gzFile *fp, FILE **ucfp) { + char *newname = malloc(strlen(name) + 10); + + rmdCacheFileN(name, &newname, n); + if (*fp == NULL) { + fclose(*ucfp); + *ucfp = fopen(newname, "rb"); + } else { + gzclose(*fp); + *fp = gzopen(newname, "rb"); + } + free(newname); + + return ((*fp == NULL) && (*ucfp == NULL)); +} + +int rmdPurgeCache(CacheData *cache_data_t, int sound) { + struct stat buff; + char *fname; + int exit_value = 0; + int nth_cache = 1; + + fname = malloc(strlen(cache_data_t->imgdata) + 10); + strcpy(fname, cache_data_t->imgdata); + + while (stat(fname, &buff) == 0) { + if (remove(fname)) { + fprintf(stderr, "Couldn't remove temporary file %s", cache_data_t->imgdata); + exit_value = 1; + } + rmdCacheFileN(cache_data_t->imgdata, &fname, nth_cache); + nth_cache++; + } + + free(fname); + + if (sound) { + if (remove(cache_data_t->audiodata)) { + fprintf(stderr, "Couldn't remove temporary file %s", cache_data_t->audiodata); + exit_value = 1; + } + } + + if (remove(cache_data_t->specsfile)) { + fprintf(stderr, "Couldn't remove temporary file %s", cache_data_t->specsfile); + exit_value = 1; + } + + if (remove(cache_data_t->projname)) { + fprintf(stderr, "Couldn't remove temporary directory %s", cache_data_t->projname); + exit_value = 1; + } + + return exit_value; +} + +void rmdInitCacheData(ProgData *pdata, EncData *enc_data_t, CacheData *cache_data_t) { + int width, height, pid; + char pidbuf[8]; + + //we set the buffer only since there's + //no need to initialize the encoder from now. + width = ((pdata->brwin.rrect.width + 15) >> 4) << 4; + height = ((pdata->brwin.rrect.height + 15) >> 4) << 4; + + (pdata)->enc_data = enc_data_t; + + enc_data_t->yuv.y = (unsigned char *)malloc(height * width); + enc_data_t->yuv.u = (unsigned char *)malloc(height * width / 4); + enc_data_t->yuv.v = (unsigned char *)malloc(height * width / 4); + enc_data_t->yuv.y_width = width; + enc_data_t->yuv.y_height = height; + enc_data_t->yuv.y_stride = width; + + enc_data_t->yuv.uv_width = width / 2; + enc_data_t->yuv.uv_height = height / 2; + enc_data_t->yuv.uv_stride = width / 2; + + //now we set the cache files + (pdata)->cache_data = cache_data_t; + + cache_data_t->workdir = (pdata->args).workdir; + pid = getpid(); + + snprintf( pidbuf, 8, "%d", pid ); + //names are stored relatively to current dir(i.e. no chdir) + cache_data_t->projname = malloc(strlen(cache_data_t->workdir) + 12 + strlen(pidbuf) + 3); + //projname + strcpy(cache_data_t->projname, cache_data_t->workdir); + strcat(cache_data_t->projname, "/"); + strcat(cache_data_t->projname, "rMD-session-"); + strcat(cache_data_t->projname, pidbuf); + strcat(cache_data_t->projname, "/"); + //image data + cache_data_t->imgdata = malloc(strlen(cache_data_t->projname) + 11); + strcpy(cache_data_t->imgdata, cache_data_t->projname); + strcat(cache_data_t->imgdata, "img.out"); + //audio data + cache_data_t->audiodata = malloc(strlen(cache_data_t->projname) + 10); + strcpy(cache_data_t->audiodata, cache_data_t->projname); + strcat(cache_data_t->audiodata, "audio.pcm"); + //specsfile + cache_data_t->specsfile = malloc(strlen(cache_data_t->projname) + 10); + strcpy(cache_data_t->specsfile, cache_data_t->projname); + strcat(cache_data_t->specsfile, "specs.txt"); + + //now that've got out buffers and our filenames we start + //creating the needed files + + if (mkdir(cache_data_t->projname, 0777)) { + fprintf(stderr, "Could not create temporary directory %s !!!\n", cache_data_t->projname); + exit(13); + } + + if (!pdata->args.zerocompression) { + cache_data_t->ifp = gzopen(cache_data_t->imgdata, "wb0f"); + if (cache_data_t->ifp == NULL) { + fprintf(stderr, "Could not create temporary file %s !!!\n", cache_data_t->imgdata); + exit(13); + } + } else { + cache_data_t->uncifp = fopen(cache_data_t->imgdata, "wb0f"); + if (cache_data_t->uncifp == NULL) { + fprintf(stderr, "Could not create temporary file %s !!!\n", cache_data_t->imgdata); + exit(13); + } + } + + if (!pdata->args.nosound) { + cache_data_t->afp = fopen(cache_data_t->audiodata, "wb"); + if (cache_data_t->afp == NULL) { + fprintf(stderr, "Could not create temporary file %s !!!\n", cache_data_t->audiodata); + exit(13); + } + } + + if (rmdWriteSpecsFile(pdata)) { + fprintf(stderr, "Could not write specsfile %s !!!\n", cache_data_t->specsfile); + exit(13); + } +} diff --git a/src/rmd_cache.h b/src/rmd_cache.h new file mode 100644 index 0000000..9f4acce --- /dev/null +++ b/src/rmd_cache.h @@ -0,0 +1,94 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_CACHE_H +#define RMD_CACHE_H 1 + +#include "rmd_types.h" + +#include <zlib.h> + +#include <stdio.h> + + + +/** +* Change file pointer to a new file while writting +* (file name is incremented with CacheFileN) +* +* \param name base file name +* +* \param n number to be used as a postfix +* +* \param fp File pointer if compression is used(must be NULL otherwise) +* +* \param ucfp File pointer if compression is NOT used(must be NULL otherwise) +* +* \returns 0 on Success 1 on Failure +*/ +int rmdSwapCacheFilesWrite(char *name,int n,gzFile *fp,FILE **ucfp); + +/** +* Change file pointer to a new file while reading +* (file name is incremented with CacheFileN) +* +* \param name base file name +* +* \param n number to be used as a postfix +* +* \param fp File pointer if compression is used(must be NULL otherwise) +* +* \param ucfp File pointer if compression is NOT used(must be NULL otherwise) +* +* \returns 0 on Success 1 on Failure +*/ +int rmdSwapCacheFilesRead(char *name,int n,gzFile *fp,FILE **ucfp); + +/** +* Delete all cache files +* +* \param cache_data_t Caching options(file names etc.) +* +* \returns 0 if all files and folders where deleted, 1 otherwise +*/ +int rmdPurgeCache(CacheData *cache_data_t,int sound); + +/** +* Initializes paths and everything else needed to start caching +* +* \param pdata ProgData struct containing all program data +* +* \param enc_data_t Encoding options +* +* \param cache_data_t Caching options +* +*/ +void rmdInitCacheData(ProgData *pdata, + EncData *enc_data_t, + CacheData *cache_data_t); + + +#endif diff --git a/src/rmd_cache_audio.c b/src/rmd_cache_audio.c new file mode 100644 index 0000000..fabcb39 --- /dev/null +++ b/src/rmd_cache_audio.c @@ -0,0 +1,104 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_cache_audio.h" + +#include "rmd_jack.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + + + +void *rmdCacheSoundBuffer(ProgData *pdata) { + size_t write_size = pdata->periodsize * pdata->sound_framesize; + +#ifdef HAVE_LIBJACK + void *jackbuf = NULL; + + if (pdata->args.use_jack) { + write_size = pdata->sound_framesize * pdata->jdata->buffersize; + jackbuf = malloc(pdata->sound_framesize * pdata->jdata->buffersize); + } +#endif + +//We are simply going to throw sound on the disk. +//It's sound is tiny compared to that of image, so +//compressing would reducethe overall size by only an +//insignificant fraction. + + while (pdata->running) { + SndBuffer *buff = NULL; + + pthread_mutex_lock(&pdata->pause_mutex); + while (pdata->paused) + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + + if (!pdata->args.use_jack) { + pthread_mutex_lock(&pdata->sound_buffer_mutex); + while (!pdata->sound_buffer && pdata->running) + pthread_cond_wait(&pdata->sound_data_read, &pdata->sound_buffer_mutex); + + buff = pdata->sound_buffer; + if (buff) + pdata->sound_buffer = buff->next; + pthread_mutex_unlock(&pdata->sound_buffer_mutex); + + if (!pdata->running) + break; + + fwrite(buff->data, 1, write_size, pdata->cache_data->afp); + free(buff->data); + free(buff); + } else { +#ifdef HAVE_LIBJACK + pthread_mutex_lock(&pdata->sound_buffer_mutex); + while ( pdata->running && + jack_ringbuffer_read_space(pdata->jdata->sound_buffer) < write_size) + pthread_cond_wait(&pdata->sound_data_read, &pdata->sound_buffer_mutex); + + if (pdata->running) + jack_ringbuffer_read(pdata->jdata->sound_buffer, jackbuf, write_size); + pthread_mutex_unlock(&pdata->sound_buffer_mutex); + + if (!pdata->running) + break; + + fwrite(jackbuf, 1, write_size, pdata->cache_data->afp); +#endif + } + pdata->avd -= pdata->periodtime; + } + + fclose(pdata->cache_data->afp); + pthread_exit(&errno); +} diff --git a/src/rmd_cache_audio.h b/src/rmd_cache_audio.h new file mode 100644 index 0000000..1ba3221 --- /dev/null +++ b/src/rmd_cache_audio.h @@ -0,0 +1,42 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef CACHE_AUDIO_H +#define CACHE_AUDIO_H 1 + +#include "rmd_types.h" + + +/** +* Sound caching thread. Simply writes the pcm buffers on disk +* +* \param pdata ProgData struct containing all program data +* +*/ +void *rmdCacheSoundBuffer(ProgData *pdata); + + +#endif diff --git a/src/rmd_cache_frame.c b/src/rmd_cache_frame.c new file mode 100644 index 0000000..7b8a2f3 --- /dev/null +++ b/src/rmd_cache_frame.c @@ -0,0 +1,319 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_cache_frame.h" + +#include "rmd_yuv_utils.h" +#include "rmd_cache.h" +#include "rmd_types.h" + +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <math.h> + + +#define BYTES_PER_MB (1024 * 1024) +#define CACHE_OUT_BUFFER_SIZE (4 * 1024) +#define CACHE_FILE_SIZE_LIMIT (500 * 1024 * 1024) + + +static int rmdFlushBlock( + unsigned char *buf, + int blockno, + int width, + int height, + int blockwidth, + gzFile fp, + FILE *ucfp, + int flush) { + + int bytes_written = 0, + block_i = (!blockwidth) ? 0 : (blockno / (width / blockwidth)),//place on the grid + block_k = (!blockwidth) ? 0 : (blockno % (width / blockwidth)); + register unsigned char *buf_reg = (&buf[(block_i * width + block_k) * blockwidth]); + static unsigned char out_buffer[CACHE_OUT_BUFFER_SIZE]; + static unsigned int out_buffer_bytes = 0; + + if (out_buffer_bytes + pow(blockwidth, 2) >= CACHE_OUT_BUFFER_SIZE || (flush && out_buffer_bytes)) { + if (ucfp == NULL) + gzwrite(fp, (void *)out_buffer, out_buffer_bytes); + else + fwrite((void *)out_buffer, 1, out_buffer_bytes, ucfp); + + bytes_written = out_buffer_bytes; + out_buffer_bytes = 0; + } + + if (!flush) { + register unsigned char *out_buf_reg = &out_buffer[out_buffer_bytes]; + + for (int j = 0;j < blockwidth; j++) { + + for(int i = 0;i < blockwidth; i++) + (*out_buf_reg++) = (*buf_reg++); + + out_buffer_bytes += blockwidth; + buf_reg += width - blockwidth; + } + } + + return bytes_written; +} + +void *rmdCacheImageBuffer(ProgData *pdata) { + + gzFile fp = NULL; + FILE *ucfp = NULL; + int index_entry_size = sizeof(u_int32_t), + blocknum_x = pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH, + blocknum_y = pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH, + firstrun = 1, + frameno = 0, + nbytes = 0, + nth_cache = 1; + u_int32_t ynum, unum, vnum, + y_short_blocks[blocknum_x * blocknum_y], + u_short_blocks[blocknum_x * blocknum_y], + v_short_blocks[blocknum_x * blocknum_y]; + unsigned long long int total_bytes = 0; + unsigned long long int total_received_bytes = 0; + unsigned int capture_frameno = 0; + + if (!pdata->args.zerocompression) { + fp = pdata->cache_data->ifp; + + if (fp == NULL) + exit(13); + } else { + ucfp = pdata->cache_data->uncifp; + + if (ucfp == NULL) + exit(13); + } + + while (pdata->running) { + FrameHeader fheader; + + ynum = unum = vnum = 0; + + pthread_mutex_lock(&pdata->img_buff_ready_mutex); + while (pdata->running && capture_frameno >= pdata->capture_frameno) + pthread_cond_wait(&pdata->image_buffer_ready, &pdata->img_buff_ready_mutex); + + capture_frameno = pdata->capture_frameno; + pthread_mutex_unlock(&pdata->img_buff_ready_mutex); + + pthread_mutex_lock(&pdata->pause_mutex); + while (pdata->paused) + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + + pthread_mutex_lock(&pdata->yuv_mutex); + + //find and flush different blocks + if (firstrun) { + firstrun = 0; + for(int j = 0; j < blocknum_x * blocknum_y; j++) { + yblocks[ynum] = 1; + y_short_blocks[ynum] = j; + ynum++; + ublocks[unum] = 1; + u_short_blocks[unum] = j; + unum++; + vblocks[vnum] = 1; + v_short_blocks[vnum] = j; + vnum++; + } + } else { + /**COMPRESS ARRAYS*/ + for(int j = 0; j < blocknum_x * blocknum_y; j++) { + if (yblocks[j]) { + y_short_blocks[ynum] = j; + ynum++; + } + if (ublocks[j]) { + u_short_blocks[unum] = j; + unum++; + } + if (vblocks[j]) { + v_short_blocks[vnum] = j; + vnum++; + } + } + } + + /**WRITE FRAME TO DISK*/ + if (!pdata->args.zerocompression) { + if (ynum * 4 + unum + vnum > (blocknum_x * blocknum_y * 6) / 10) + gzsetparams(fp, 1, Z_FILTERED); + else + gzsetparams(fp, 0, Z_FILTERED); + } + + strncpy(fheader.frame_prefix, "FRAM", 4); + fheader.frameno = ++frameno; + fheader.current_total = pdata->frames_total; + + fheader.Ynum = ynum; + fheader.Unum = unum; + fheader.Vnum = vnum; + + if (!pdata->args.zerocompression) { + nbytes += gzwrite(fp, (void*)&fheader, sizeof(FrameHeader)); + //flush indexes + if (ynum) + nbytes += gzwrite(fp, (void*)y_short_blocks, ynum * index_entry_size); + + if (unum) + nbytes += gzwrite(fp, (void*)u_short_blocks, unum * index_entry_size); + + if (vnum) + nbytes += gzwrite(fp, (void*)v_short_blocks, vnum * index_entry_size); + } else { + nbytes += sizeof(FrameHeader)* + fwrite((void*)&fheader, sizeof(FrameHeader), 1, ucfp); + //flush indexes + if (ynum) + nbytes += index_entry_size * fwrite(y_short_blocks, index_entry_size, ynum, ucfp); + + if (unum) + nbytes += index_entry_size * fwrite(u_short_blocks, index_entry_size, unum, ucfp); + + if (vnum) + nbytes += index_entry_size * fwrite(v_short_blocks, index_entry_size, vnum, ucfp); + } + + //flush the blocks for each buffer + if (ynum) { + for(int j = 0; j < ynum; j++) + nbytes += rmdFlushBlock(pdata->enc_data->yuv.y, + y_short_blocks[j], + pdata->enc_data->yuv.y_width, + pdata->enc_data->yuv.y_height, + Y_UNIT_WIDTH, + fp, + ucfp, + 0); + } + + if (unum) { + for(int j = 0; j < unum; j++) + nbytes += rmdFlushBlock(pdata->enc_data->yuv.u, + u_short_blocks[j], + pdata->enc_data->yuv.uv_width, + pdata->enc_data->yuv.uv_height, + UV_UNIT_WIDTH, + fp, + ucfp, + 0); + } + + if (vnum) { + for(int j = 0; j < vnum; j++) + nbytes += rmdFlushBlock(pdata->enc_data->yuv.v, + v_short_blocks[j], + pdata->enc_data->yuv.uv_width, + pdata->enc_data->yuv.uv_height, + UV_UNIT_WIDTH, + fp, + ucfp, + 0); + } + + //release main buffer + pthread_mutex_unlock(&pdata->yuv_mutex); + + nbytes += rmdFlushBlock(NULL, 0, 0, 0, 0, fp, ucfp, 1); + /**@________________@**/ + pdata->avd += pdata->frametime; + + if (nbytes > CACHE_FILE_SIZE_LIMIT) { + if (rmdSwapCacheFilesWrite(pdata->cache_data->imgdata, nth_cache, &fp, &ucfp)) { + fprintf(stderr, "New cache file could not be created.\n" + "Ending recording...\n"); + fflush(stderr); + raise(SIGINT); //if for some reason we cannot make a new file + //we have to stop. If we are out of space, + //which means + //that encoding cannot happen either, + //InitEncoder will cause an abrupt end with an + //error code and the cache will remain intact. + //If we've chosen separate two-stages, + //the program will make a + //clean exit. + //In either case data will be preserved so if + //space is freed the recording + //can be proccessed later. + } + total_bytes += nbytes; + nth_cache++; + nbytes = 0; + } + } + total_bytes += nbytes; + + { + unsigned int bytes_per_pixel = pdata->specs.depth >= 24 ? 4 : 2; + unsigned int pixels_per_frame = pdata->enc_data->yuv.y_width * pdata->enc_data->yuv.y_height; + + total_received_bytes = ((unsigned long long int)frameno) * bytes_per_pixel * pixels_per_frame; + } + + if (total_received_bytes) { + double percent_of_data_left = (total_bytes / (double)total_received_bytes) * 100; + + fprintf(stderr, "\n" + "*********************************************\n" + "\n" + "Cached %llu MB, from %llu MB that were received.\n" + "Average cache compression ratio: %.1f %%\n" + "\n" + "*********************************************\n", + total_bytes / BYTES_PER_MB, + total_received_bytes / BYTES_PER_MB, + 100 - percent_of_data_left); + + } + + fprintf(stderr, "Saved %d frames in a total of %d requests\n", + frameno, + pdata->frames_total); + fflush(stderr); + + if (!pdata->args.zerocompression) { + gzflush(fp, Z_FINISH); + gzclose(fp); + } else { + fflush(ucfp); + fclose(ucfp); + } + + pthread_exit(&errno); +} diff --git a/src/rmd_cache_frame.h b/src/rmd_cache_frame.h new file mode 100644 index 0000000..9361e35 --- /dev/null +++ b/src/rmd_cache_frame.h @@ -0,0 +1,43 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef CACHE_FRAME_H +#define CACHE_FRAME_H 1 + +#include "rmd_types.h" + + +/** +* Image caching thread. Copies the yuv buffer, compares with the last one and +* caches the result. +* +* \param pdata ProgData struct containing all program data +* +*/ +void *rmdCacheImageBuffer(ProgData *pdata); + + +#endif diff --git a/src/rmd_capture_sound.c b/src/rmd_capture_sound.c new file mode 100644 index 0000000..2719c60 --- /dev/null +++ b/src/rmd_capture_sound.c @@ -0,0 +1,165 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_capture_sound.h" + +#include "rmd_jack.h" +#include "rmd_opendev.h" +#include "rmd_types.h" + +#include <pthread.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <unistd.h> +#include <stdlib.h> + + +void *rmdCaptureSound(ProgData *pdata) { + +#ifdef HAVE_LIBASOUND + int frames = pdata->periodsize; +#endif + //start capturing only after first frame is taken + usleep(pdata->frametime); + + while (pdata->running) { + int sret = 0; + SndBuffer *newbuf, *tmp; + + if (pdata->paused) { +#ifdef HAVE_LIBASOUND + if (!pdata->hard_pause) { + snd_pcm_pause(pdata->sound_handle, 1); + pthread_mutex_lock(&pdata->pause_mutex); + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + snd_pcm_pause(pdata->sound_handle, 0); + } else {//device doesn't support pause(is this the norm?mine doesn't) + snd_pcm_close(pdata->sound_handle); + pthread_mutex_lock(&pdata->pause_mutex); + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + pdata->sound_handle = + rmdOpenDev(pdata->args.device, + &pdata->args.channels, + &pdata->args.frequency, + &pdata->args.buffsize, + NULL, + NULL, + NULL//let's hope that the device capabilities + //didn't magically change + ); + if (pdata->sound_handle == NULL) { + fprintf(stderr, "Couldn't reopen sound device.Exiting\n"); + pdata->running = FALSE; + errno = 3; + pthread_exit(&errno); + } + } +#else + close(pdata->sound_handle); + pthread_mutex_lock(&pdata->pause_mutex); + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + pdata->sound_handle = rmdOpenDev( pdata->args.device, + pdata->args.channels, + pdata->args.frequency); + if (pdata->sound_handle < 0) { + fprintf(stderr, "Couldn't reopen sound device. Exiting\n"); + pdata->running = FALSE; + errno = 3; + pthread_exit(&errno); + } +#endif + } + + //create new buffer + newbuf = (SndBuffer *)malloc(sizeof(SndBuffer)); +#ifdef HAVE_LIBASOUND + newbuf->data = (signed char *)malloc(frames * pdata->sound_framesize); +#else + newbuf->data = (signed char *)malloc((pdata->args.buffsize << 1) * pdata->args.channels); +#endif + newbuf->next = NULL; + + //read data into new buffer +#ifdef HAVE_LIBASOUND + while (sret<frames) { + int temp_sret = snd_pcm_readi( pdata->sound_handle, + newbuf->data + pdata->sound_framesize * sret, + frames-sret); + if (temp_sret == -EPIPE) { + fprintf(stderr, "%s: Overrun occurred.\n", + snd_strerror(temp_sret)); + snd_pcm_prepare(pdata->sound_handle); + } else if (temp_sret < 0) { + fprintf(stderr, "An error occured while reading sound data:\n" + " %s\n", + snd_strerror(temp_sret)); + snd_pcm_prepare(pdata->sound_handle); + } else + sret += temp_sret; + } +#else + sret = 0; + //oss recording loop + do { + int temp_sret = read( pdata->sound_handle, + &newbuf->data[sret], + (pdata->args.buffsize << 1) * + pdata->args.channels)-sret; + if (temp_sret < 0) { + fprintf(stderr, "An error occured while reading from soundcard" + "%s\nError description:\n%s\n", + pdata->args.device, strerror(errno)); + } else + sret += temp_sret; + } while (sret < (pdata->args.buffsize << 1) * pdata->args.channels); +#endif + //queue the new buffer + pthread_mutex_lock(&pdata->sound_buffer_mutex); + tmp = pdata->sound_buffer; + if (!tmp) + pdata->sound_buffer = newbuf; + else { + while (tmp->next != NULL) + tmp = tmp->next; + + tmp->next = newbuf; + } + pthread_cond_signal(&pdata->sound_data_read); + pthread_mutex_unlock(&pdata->sound_buffer_mutex); + } +#ifdef HAVE_LIBASOUND + snd_pcm_close(pdata->sound_handle); +#else + close(pdata->sound_handle); +#endif + pthread_exit(&errno); +} diff --git a/src/rmd_capture_sound.h b/src/rmd_capture_sound.h new file mode 100644 index 0000000..b403fa8 --- /dev/null +++ b/src/rmd_capture_sound.h @@ -0,0 +1,42 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef CAPTURE_SOUND_H +#define CAPTURE_SOUND_H 1 + +#include "rmd_types.h" + + +/** +* Sound capturing thread. Data are placed on a +* list to be picked up by other threads. +* +* \param pdata ProgData struct containing all program data +*/ +void *rmdCaptureSound(ProgData *pdata); + + +#endif diff --git a/src/rmd_encode_cache.c b/src/rmd_encode_cache.c new file mode 100644 index 0000000..1e0727c --- /dev/null +++ b/src/rmd_encode_cache.c @@ -0,0 +1,72 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_encode_cache.h" + +#include "rmd_flush_to_ogg.h" +#include "rmd_init_encoder.h" +#include "rmd_load_cache.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdio.h> +#include <stdlib.h> + + + +void rmdEncodeCache(ProgData *pdata){ + pthread_t flush_to_ogg_t, load_cache_t; + + fprintf(stderr, "STATE:ENCODING\n"); + fprintf(stderr, "Encoding started!\nThis may take several minutes.\n" + "Pressing Ctrl-C will cancel the procedure" + " (resuming will not be possible, but\n" + "any portion of the video, which is already encoded won't be deleted).\n" + "Please wait...\n"); + + pdata->running = TRUE; + rmdInitEncoder(pdata, pdata->enc_data, 1); + //load encoding and flushing threads + if (!pdata->args.nosound) { + //before we start loading again + //we need to free any left-overs + while (pdata->sound_buffer != NULL) { + free(pdata->sound_buffer->data); + pdata->sound_buffer = pdata->sound_buffer->next; + } + } + pthread_create(&flush_to_ogg_t, NULL, (void *)rmdFlushToOgg, (void *)pdata); + + //start loading image and audio + pthread_create(&load_cache_t, NULL, (void *)rmdLoadCache, (void *)pdata); + + //join and finish + pthread_join(load_cache_t, NULL); + fprintf(stderr, "Encoding finished!\nWait a moment please...\n"); + pthread_join(flush_to_ogg_t, NULL); +} diff --git a/src/rmd_encode_cache.h b/src/rmd_encode_cache.h new file mode 100644 index 0000000..4c37f97 --- /dev/null +++ b/src/rmd_encode_cache.h @@ -0,0 +1,41 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef ENCODE_CACHE_H +#define ENCODE_CACHE_H 1 + +#include "rmd_types.h" + + +/** +* Encode cache into an +* ogg stream. +* \param pdata ProgData struct containing all program data +*/ +void rmdEncodeCache(ProgData *pdata); + + +#endif diff --git a/src/rmd_encode_image_buffer.c b/src/rmd_encode_image_buffer.c new file mode 100644 index 0000000..c234aa4 --- /dev/null +++ b/src/rmd_encode_image_buffer.c @@ -0,0 +1,99 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_encode_image_buffer.h" + +#include "rmd_types.h" + +#include <errno.h> + + +void *rmdEncodeImageBuffer(ProgData *pdata) { + unsigned int encode_frameno = 0; + + pdata->th_encoding_clean = 0; + + while (pdata->running) { + EncData *enc_data = pdata->enc_data; + + pthread_mutex_lock(&pdata->img_buff_ready_mutex); + while (pdata->running && encode_frameno >= pdata->capture_frameno) + pthread_cond_wait(&pdata->image_buffer_ready, &pdata->img_buff_ready_mutex); + encode_frameno = pdata->capture_frameno; + pthread_mutex_unlock(&pdata->img_buff_ready_mutex); + + pthread_mutex_lock(&pdata->pause_mutex); + while (pdata->paused) + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + + pthread_mutex_lock(&pdata->yuv_mutex); + if (theora_encode_YUVin(&enc_data->m_th_st, &enc_data->yuv)) { + fprintf(stderr, "Encoder not ready!\n"); + pthread_mutex_unlock(&pdata->yuv_mutex); + } else { + pthread_mutex_unlock(&pdata->yuv_mutex); + + if (theora_encode_packetout(&enc_data->m_th_st, 0, &enc_data->m_ogg_pckt1) == 1) { + pthread_mutex_lock(&pdata->libogg_mutex); + ogg_stream_packetin(&enc_data->m_ogg_ts, &enc_data->m_ogg_pckt1); + pdata->avd += pdata->frametime; + pthread_mutex_unlock(&pdata->libogg_mutex); + } + } + } + + //last packet + pthread_mutex_lock(&pdata->theora_lib_mutex); + pdata->th_encoding_clean = 1; + pthread_cond_signal(&pdata->theora_lib_clean); + pthread_mutex_unlock(&pdata->theora_lib_mutex); + + pthread_exit(&errno); +} + +//this function is meant to be called normally +//not through a thread of it's own +void rmdSyncEncodeImageBuffer(ProgData *pdata) { + EncData *enc_data = pdata->enc_data; + + if (theora_encode_YUVin(&enc_data->m_th_st, &enc_data->yuv)) { + fprintf(stderr, "Encoder not ready!\n"); + return; + } + + if (theora_encode_packetout(&enc_data->m_th_st, !pdata->running, &enc_data->m_ogg_pckt1) == 1) { + pthread_mutex_lock(&pdata->libogg_mutex); + ogg_stream_packetin(&enc_data->m_ogg_ts, &enc_data->m_ogg_pckt1); + + if (!pdata->running) + enc_data->m_ogg_ts.e_o_s = 1; + + pdata->avd += pdata->frametime; + pthread_mutex_unlock(&pdata->libogg_mutex); + } +} diff --git a/src/rmd_encode_image_buffer.h b/src/rmd_encode_image_buffer.h new file mode 100644 index 0000000..e5ca3db --- /dev/null +++ b/src/rmd_encode_image_buffer.h @@ -0,0 +1,50 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef ENCODE_IMAGE_BUFFER_H +#define ENCODE_IMAGE_BUFFER_H 1 + +#include "rmd_types.h" + + +/** +* feed a yuv buffer to the theora encoder and submit outcome to +* the ogg stream. +* \param pdata ProgData struct containing all program data +*/ +void *rmdEncodeImageBuffer(ProgData *pdata); + +/** +* As rmdEncodeImageBuffer, only with the assumption that +* this is not a thread on it's own +* +* \param pdata ProgData struct containing all program data +* +*/ +void rmdSyncEncodeImageBuffer(ProgData *pdata); + + +#endif diff --git a/src/rmd_encode_sound_buffer.c b/src/rmd_encode_sound_buffer.c new file mode 100644 index 0000000..5fa1dc6 --- /dev/null +++ b/src/rmd_encode_sound_buffer.c @@ -0,0 +1,178 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_encode_sound_buffer.h" + +#include "rmd_jack.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdlib.h> +#include <errno.h> + + +void *rmdEncodeSoundBuffer(ProgData *pdata) { + int sampread = pdata->periodsize; +#ifdef HAVE_LIBJACK + void *jackbuf = NULL; + + if (pdata->args.use_jack) + jackbuf = malloc(pdata->sound_framesize * pdata->jdata->buffersize); +#endif + + pdata->v_encoding_clean = 0; + while (pdata->running) { + float **vorbis_buffer; + SndBuffer *buff = NULL; + + pthread_mutex_lock(&pdata->pause_mutex); + while (pdata->paused) + pthread_cond_wait(&pdata->pause_cond, &pdata->pause_mutex); + pthread_mutex_unlock(&pdata->pause_mutex); + + if (!pdata->args.use_jack) { + pthread_mutex_lock(&pdata->sound_buffer_mutex); + while (!pdata->sound_buffer && pdata->running) + pthread_cond_wait(&pdata->sound_data_read, &pdata->sound_buffer_mutex); + + buff = pdata->sound_buffer; + if (buff) + pdata->sound_buffer = buff->next; + pthread_mutex_unlock(&pdata->sound_buffer_mutex); + + if (!pdata->running) + break; + + vorbis_buffer = vorbis_analysis_buffer(&pdata->enc_data->m_vo_dsp, sampread); + + for (int i = 0, count = 0; i < sampread; i++) { + for (int j = 0; j < pdata->args.channels; j++) { + vorbis_buffer[j][i] = ((buff->data[count + 1] << 8) | + (0x00ff & (int)buff->data[count])) / + 32768.f; + count += 2; + } + } + free(buff->data); + free(buff); + } else { +#ifdef HAVE_LIBJACK + + pthread_mutex_lock(&pdata->sound_buffer_mutex); + while ( pdata->running && + jack_ringbuffer_read_space(pdata->jdata->sound_buffer) < + pdata->sound_framesize * pdata->jdata->buffersize) + pthread_cond_wait(&pdata->sound_data_read, &pdata->sound_buffer_mutex); + + if (pdata->running) + jack_ringbuffer_read( pdata->jdata->sound_buffer, + jackbuf, + (pdata->sound_framesize * + pdata->jdata->buffersize) + ); + + pthread_mutex_unlock(&pdata->sound_buffer_mutex); + + if (!pdata->running) + break; + + vorbis_buffer = vorbis_analysis_buffer(&pdata->enc_data->m_vo_dsp, sampread); + + for (int j = 0, count = 0; j < pdata->args.channels; j++) { + for (int i = 0; i < sampread; i++) { + vorbis_buffer[j][i] = ((float*)jackbuf)[count]; + count++; + } + } +#endif + } + vorbis_analysis_wrote(&pdata->enc_data->m_vo_dsp, sampread); + + pthread_mutex_lock(&pdata->libogg_mutex); + while (vorbis_analysis_blockout(&pdata->enc_data->m_vo_dsp, &pdata->enc_data->m_vo_block) == 1) { + + vorbis_analysis(&pdata->enc_data->m_vo_block, NULL); + vorbis_bitrate_addblock(&pdata->enc_data->m_vo_block); + + while (vorbis_bitrate_flushpacket(&pdata->enc_data->m_vo_dsp, &pdata->enc_data->m_ogg_pckt2)) { + ogg_stream_packetin(&pdata->enc_data->m_ogg_vs, &pdata->enc_data->m_ogg_pckt2); + } + } + pthread_mutex_unlock(&pdata->libogg_mutex); + + /* this needs synchronizing */ + pdata->avd -= pdata->periodtime; + } + + pthread_mutex_lock(&pdata->vorbis_lib_mutex); + pdata->v_encoding_clean = 1; + pthread_cond_signal(&pdata->vorbis_lib_clean); + pthread_mutex_unlock(&pdata->vorbis_lib_mutex); + pthread_exit(&errno); +} + +void rmdSyncEncodeSoundBuffer(ProgData *pdata, signed char *buff) { + int sampread = (buff != NULL) ? pdata->periodsize : 0; + float **vorbis_buffer = vorbis_analysis_buffer(&pdata->enc_data->m_vo_dsp, sampread); + + if (!pdata->args.use_jack) { + for (int i = 0, count = 0; i < sampread; i++) { + for (int j = 0; j < pdata->args.channels; j++) { + vorbis_buffer[j][i] = ((buff[count + 1] << 8) | + (0x00ff & (int)buff[count])) + / 32768.f; + count += 2; + } + } + } else { + for (int j = 0, count = 0; j < pdata->args.channels; j++) { + for (int i = 0; i < sampread; i++) { + vorbis_buffer[j][i] = ((float *)buff)[count]; + count++; + } + } + } + + vorbis_analysis_wrote(&pdata->enc_data->m_vo_dsp, sampread); + + pthread_mutex_lock(&pdata->libogg_mutex); + while (vorbis_analysis_blockout(&pdata->enc_data->m_vo_dsp, &pdata->enc_data->m_vo_block) == 1) { + + vorbis_analysis(&pdata->enc_data->m_vo_block, NULL); + vorbis_bitrate_addblock(&pdata->enc_data->m_vo_block); + + while (vorbis_bitrate_flushpacket(&pdata->enc_data->m_vo_dsp, &pdata->enc_data->m_ogg_pckt2)) + ogg_stream_packetin(&pdata->enc_data->m_ogg_vs, &pdata->enc_data->m_ogg_pckt2); + } + pthread_mutex_unlock(&pdata->libogg_mutex); + + if (!pdata->running) + pdata->enc_data->m_ogg_vs.e_o_s = 1; + + pdata->avd -= pdata->periodtime; +} diff --git a/src/rmd_encode_sound_buffer.h b/src/rmd_encode_sound_buffer.h new file mode 100644 index 0000000..7d5c312 --- /dev/null +++ b/src/rmd_encode_sound_buffer.h @@ -0,0 +1,51 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef ENCODE_SOUND_BUFFER_H +#define ENCODE_SOUND_BUFFER_H 1 + +#include "rmd_types.h" + + +/** +* Sound encoding thread. Picks up data from the buffer queue , +* encodes and places them on the vorbis stream. +* +* \param pdata ProgData struct containing all program data +*/ +void *rmdEncodeSoundBuffer(ProgData *pdata); + +/** +* As rmdEncodeSoundBuffer, only with the assumption that +* this is not a thread on it's own +* +* \param pdata ProgData struct containing all program data +* +*/ +void rmdSyncEncodeSoundBuffer(ProgData *pdata,signed char *buff); + + +#endif diff --git a/src/rmd_error.c b/src/rmd_error.c new file mode 100644 index 0000000..ba95198 --- /dev/null +++ b/src/rmd_error.c @@ -0,0 +1,59 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_error.h" + +#include <X11/Xlib.h> +#include <X11/Xlibint.h> + +#include <stdio.h> +#include <stdlib.h> + + + +int rmdErrorHandler(Display *dpy, XErrorEvent *e) +{ + char error_desc[1024]; + + XGetErrorText(dpy, e->error_code, error_desc, sizeof(error_desc)); + fprintf(stderr, "X Error: %s\n", error_desc); + fflush(stderr); + + if ((e->error_code==BadWindow)&&(e->request_code==X_GetWindowAttributes)) { + fprintf(stderr, "BadWindow on XGetWindowAttributes.\nIgnoring...\n"); + fflush(stderr); + return 0; + } + + if ((e->error_code==BadAccess)&&(e->request_code==X_GrabKey)) { + fprintf(stderr, "Bad Access on XGrabKey.\n" "Shortcut already assigned.\n"); + fflush(stderr); + return 0; + } + + exit(1); +} diff --git a/src/rmd_error.h b/src/rmd_error.h new file mode 100644 index 0000000..4d27264 --- /dev/null +++ b/src/rmd_error.h @@ -0,0 +1,49 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_ERROR_H +#define RMD_ERROR_H 1 + +#include "rmd_types.h" + + +/* + * Handling of X errors. + * Ignores, bad access when registering shortcuts + * and BadWindow on XQueryTree + * + * \param dpy Connection to the X Server + * + * \param e XErrorEvent struct containing error info + * + * \returns 0 on the two ignored cases, calls exit(1) + * otherwise. + * + */ +int rmdErrorHandler(Display *dpy,XErrorEvent *e); + + +#endif diff --git a/src/rmd_flush_to_ogg.c b/src/rmd_flush_to_ogg.c new file mode 100644 index 0000000..87308a6 --- /dev/null +++ b/src/rmd_flush_to_ogg.c @@ -0,0 +1,235 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_flush_to_ogg.h" + +#include "rmd_encode_image_buffer.h" +#include "rmd_encode_sound_buffer.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + + + +//we copy the page because the next call to ogg_stream_pageout +//will invalidate it. But we must have pages from +//both streams at every time in +//order to do correct multiplexing +static void page_copy(ogg_page *new, ogg_page *old) { + + new->header_len = old->header_len; + new->header = malloc(new->header_len); + new->body_len = old->body_len; + new->body = malloc(new->body_len); + + memcpy(new->header, old->header, new->header_len); + memcpy(new->body, old->body, new->body_len); +} + +//free our copy +static void page_free(ogg_page *pg) { + pg->header_len = pg->body_len = 0; + free(pg->header); + free(pg->body); +} + +void *rmdFlushToOgg(ProgData *pdata) { + int videoflag = 0, audioflag = 0; + double video_bytesout = 0, audio_bytesout = 0; + ogg_page videopage, //owned by libogg + videopage_copy, //owned by the application + audiopage, //owned by libogg + audiopage_copy; //owned by the application + + double audiotime = 0; + double videotime = 0; + int th_st_fin = 0, + v_st_fin = (pdata->args.nosound); + + while (!(th_st_fin && v_st_fin)) { + int audio_or_video = 0; + + if (pdata->running) { + pthread_mutex_lock(&pdata->libogg_mutex); + if (!videoflag) { + videoflag = ogg_stream_pageout(&pdata->enc_data->m_ogg_ts, &videopage); + videotime = videoflag ? theora_granule_time( + &pdata->enc_data->m_th_st, + ogg_page_granulepos(&videopage) + ) : -1; + + if (videoflag) + page_copy(&videopage_copy, &videopage); + } + + if (!pdata->args.nosound && !audioflag) { + audioflag = ogg_stream_pageout(&pdata->enc_data->m_ogg_vs, &audiopage); + audiotime = audioflag ? vorbis_granule_time( + &pdata->enc_data->m_vo_dsp, + ogg_page_granulepos(&audiopage) + ) : -1; + + if (audioflag) + page_copy(&audiopage_copy, &audiopage); + } + pthread_mutex_unlock(&pdata->libogg_mutex); + } else { + if (!th_st_fin && !videoflag) { + pthread_mutex_lock(&pdata->libogg_mutex); + videoflag = ogg_stream_flush(&pdata->enc_data->m_ogg_ts, &videopage); + videotime = videoflag ? theora_granule_time( + &pdata->enc_data->m_th_st, + ogg_page_granulepos(&videopage) + ) : -1; + + if (videoflag) + page_copy(&videopage_copy, &videopage); + pthread_mutex_unlock(&pdata->libogg_mutex); + + //we need the last page to properly close the stream + if (!videoflag) { + pthread_mutex_lock(&pdata->theora_lib_mutex); + while (!pdata->th_encoding_clean) + pthread_cond_wait(&pdata->theora_lib_clean, &pdata->theora_lib_mutex); + pthread_mutex_unlock(&pdata->theora_lib_mutex); + rmdSyncEncodeImageBuffer(pdata); + } + } + + if (!pdata->args.nosound && !v_st_fin && !audioflag) { + pthread_mutex_lock(&pdata->libogg_mutex); + audioflag = ogg_stream_flush(&pdata->enc_data->m_ogg_vs, &audiopage); + audiotime = audioflag ? vorbis_granule_time( + &pdata->enc_data->m_vo_dsp, + ogg_page_granulepos(&audiopage) + ) : -1; + + if (audioflag) + page_copy(&audiopage_copy, &audiopage); + pthread_mutex_unlock(&pdata->libogg_mutex); + + //we need the last page to properly close the stream + if (!audioflag) { + pthread_mutex_lock(&pdata->vorbis_lib_mutex); + while (!pdata->v_encoding_clean) + pthread_cond_wait(&pdata->vorbis_lib_clean, &pdata->vorbis_lib_mutex); + pthread_mutex_unlock(&pdata->vorbis_lib_mutex); + rmdSyncEncodeSoundBuffer(pdata, NULL); + } + } + } + +#if 0 + /* I don't understand what this is about, if these are finished we want to lose + * their pages? + */ + if (th_st_fin) + videoflag=0; + + if (v_st_fin) + audioflag=0; +#endif + if ((!audioflag && !v_st_fin && !pdata->args.nosound) || (!videoflag && !th_st_fin)) { + usleep(10000); + continue; + } + + if (!audioflag) { + audio_or_video = 1; + } else if (!videoflag) { + audio_or_video = 0; + } else { + if (audiotime < videotime) + audio_or_video = 0; + else + audio_or_video = 1; + } + + if (audio_or_video == 1) { + video_bytesout += fwrite( videopage_copy.header, 1, + videopage_copy.header_len, + pdata->enc_data->fp); + + video_bytesout += fwrite( videopage_copy.body, 1, + videopage_copy.body_len, + pdata->enc_data->fp); + videoflag = 0; + + if (!pdata->running) { + pthread_mutex_lock(&pdata->libogg_mutex); + if (ogg_page_eos(&videopage_copy)) + th_st_fin = 1; + pthread_mutex_unlock(&pdata->libogg_mutex); + } + + page_free(&videopage_copy); + } else { + audio_bytesout += fwrite( audiopage_copy.header, 1, + audiopage_copy.header_len, + pdata->enc_data->fp); + + audio_bytesout += fwrite( audiopage_copy.body, 1, + audiopage_copy.body_len, + pdata->enc_data->fp); + audioflag = 0; + + if (!pdata->running) { + pthread_mutex_lock(&pdata->libogg_mutex); + if (ogg_page_eos(&audiopage_copy)) + v_st_fin = 1; + pthread_mutex_unlock(&pdata->libogg_mutex); + } + + page_free(&audiopage_copy); + } + } + + pthread_mutex_lock(&pdata->libogg_mutex); + ogg_stream_clear(&pdata->enc_data->m_ogg_ts); + + if (!pdata->args.nosound) + ogg_stream_clear(&pdata->enc_data->m_ogg_vs); + + pthread_mutex_unlock(&pdata->libogg_mutex); + + theora_clear(&pdata->enc_data->m_th_st); + + if (pdata->enc_data->fp) + fclose(pdata->enc_data->fp); + + fprintf(stderr, "\r \nDone.\nWritten %.0f bytes\n" + "(%.0f of which were video data and %.0f audio data)\n\n", + video_bytesout + audio_bytesout, + video_bytesout, audio_bytesout); + + pthread_exit(&errno); +} diff --git a/src/rmd_flush_to_ogg.h b/src/rmd_flush_to_ogg.h new file mode 100644 index 0000000..64094a8 --- /dev/null +++ b/src/rmd_flush_to_ogg.h @@ -0,0 +1,40 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef FLUSH_TO_OGG_H +#define FLUSH_TO_OGG_H 1 + +#include "rmd_types.h" + + +/** +* Query theora and vorbis streams for ready packages and +* flush them on the disk +* \param pdata ProgData struct containing all program data +*/ +void *rmdFlushToOgg(ProgData *pdata); + +#endif diff --git a/src/rmd_frame.c b/src/rmd_frame.c new file mode 100644 index 0000000..ee5c4d8 --- /dev/null +++ b/src/rmd_frame.c @@ -0,0 +1,142 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_frame.h" + +#include <X11/Xlib.h> +#include <X11/extensions/shape.h> + +#include <stdio.h> +#include <stdlib.h> + + +#define BORDER_WIDTH 6 +#define OUTLINE_WIDTH 1 + + +void rmdDrawFrame( Display *dpy, + int screen, + Window win, + int width, + int height) { + + GC gc; + XGCValues gcv; + XColor white, white_e, black, black_e; + unsigned long gcmask = GCForeground; + + XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "white", &white, &white_e); + XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "black", &black, &black_e); + + gcv.foreground = black.pixel; + gc = XCreateGC(dpy, win, gcmask, &gcv); + XFillRectangle( dpy, + win, + gc, + OUTLINE_WIDTH, + OUTLINE_WIDTH, + width + (BORDER_WIDTH-OUTLINE_WIDTH) * 2, + height + (BORDER_WIDTH-OUTLINE_WIDTH) * 2); + gcv.foreground = white.pixel; + XChangeGC(dpy, gc, gcmask, &gcv); + XFillRectangle( dpy, + win, + gc, + BORDER_WIDTH-OUTLINE_WIDTH, + BORDER_WIDTH-OUTLINE_WIDTH, + width + OUTLINE_WIDTH * 2, + height + OUTLINE_WIDTH * 2); + + XFreeGC(dpy, gc); + +} + +void rmdMoveFrame( Display *dpy, + Window win, + int x, + int y) { + + XMoveWindow(dpy, win, x-BORDER_WIDTH, y-BORDER_WIDTH); +// XSync(pdata->dpy, False); +} + +Window rmdFrameInit( Display *dpy, + int screen, + Window root, + int x, + int y, + int width, + int height) { + + XSetWindowAttributes attribs; + XColor white, white_e; + Window win; + unsigned long valuemask = CWBackPixmap|CWBackPixel| + CWSaveUnder|CWOverrideRedirect|CWColormap; + + XAllocNamedColor(dpy, DefaultColormap(dpy, screen), "white", &white, &white_e); + + attribs.background_pixmap = None; + attribs.background_pixel = white.pixel; + attribs.save_under = True; + attribs.override_redirect = True; + attribs.colormap = DefaultColormap(dpy, screen); + + win = XCreateWindow( dpy, + root, + x - BORDER_WIDTH, + y - BORDER_WIDTH, + width + BORDER_WIDTH * 2, + height + BORDER_WIDTH * 2, + 0, + CopyFromParent, + InputOutput, + CopyFromParent, + valuemask, + &attribs); + + XRectangle rect; + rect.x = rect.y = BORDER_WIDTH; + rect.width = width; + rect.height = height; + + XShapeCombineRectangles( dpy, + win, + ShapeBounding, + 0, + 0, + &rect, + 1, + ShapeSubtract, + 0); + + XMapWindow(dpy, win); + + rmdDrawFrame(dpy, screen, win, width, height); + + return win; +} diff --git a/src/rmd_frame.h b/src/rmd_frame.h new file mode 100644 index 0000000..c3969f8 --- /dev/null +++ b/src/rmd_frame.h @@ -0,0 +1,101 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_FRAME_H +#define RMD_FRAME_H 1 + +#include "rmd_types.h" + + +/* + * Create a frame that marks the recording area. + * + * \param dpy Connection to the X Server + * + * \param screen Recorded screen + * + * \param root Root window of the display + * + * \param x X pos of the recorded area + * + * \param y Y pos of the recorded area + * + * \param width Width of the recorded area + * + * \param height Height of the recorded area + * + * \returns The WindowID of the frame + * + */ +Window rmdFrameInit(Display *dpy, + int screen, + Window root, + int x, + int y, + int width, + int height); + + +/* + * Move the frame (subtracts the borderwidth) + * + * \param dpy Connection to the X Server + * + * \param win WindowId of the frame + * + * \param x New X pos of the recorded area + * + * \param y New Y pos of the recorded area + * + */ +void rmdMoveFrame(Display *dpy, + Window win, + int x, + int y); + + +/* + * Redraw the frame that marks the recording area. + * + * \param dpy Connection to the X Server + * + * \param screen Recorded screen + * + * \param win WindoID of the frame + * + * \param width Width of the recorded area + * + * \param height Height of the recorded area + * + */ +void rmdDrawFrame(Display *dpy, + int screen, + Window win, + int width, + int height); + + +#endif diff --git a/src/rmd_get_frame.c b/src/rmd_get_frame.c new file mode 100644 index 0000000..a9e4daa --- /dev/null +++ b/src/rmd_get_frame.c @@ -0,0 +1,593 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_get_frame.h" + +#include "rmd_cache.h" +#include "rmd_frame.h" +#include "rmd_getzpixmap.h" +#include "rmd_poll_events.h" +#include "rmd_rectinsert.h" +#include "rmd_update_image.h" +#include "rmd_yuv_utils.h" +#include "rmd_types.h" + +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/XShm.h> + +#include <limits.h> +#include <pthread.h> +#include <sys/shm.h> +#include <errno.h> +#include <stdlib.h> + + +/* clip_event_area dejavu */ +static void clip_dummy_pointer_area(XRectangle *area, XRectangle *clip, XRectangle *res) +{ + res->x = (((area->x + area->width >= clip->x) && + (area->x <= clip->x + clip->width)) ? + ((area->x <= clip->x) ? clip->x : area->x) : -1); + + res->y = (((area->y + area->height >= clip->y) && + (area->y <= clip->y + clip->height)) ? + ((area->y <= clip->y) ? clip->y : area->y) : -1); + + res->width = (area->x <= clip->x) ? area->width - (clip->x - area->x) : + (area->x <= clip->x + clip->width) ? + (clip->width - area->x + clip->x < area->width) ? + clip->width - area->x + clip->x : area->width : 0; + + res->height = (area->y <= clip->y) ? area->height - (clip->y - area->y) : + (area->y <= clip->y + clip->height) ? + (clip->height - area->y + clip->y < area->height) ? + clip->height - area->y + clip->y : area->height : 0; + + if (res->x + res->width > clip->x + clip->width) + res->width = clip->x + clip->width - res->x; + + if (res->y + res->height > clip->y + clip->height) + res->height = clip->y + clip->height - res->y; +} + +#define MARK_BUFFER_AREA_C( data, \ + x_tm, \ + y_tm, \ + width_tm, \ + height_tm, \ + buffer_width, \ + __depth__) { \ + \ + register u_int##__depth__##_t *datapi = \ + ((u_int##__depth__##_t *)data) + y_tm * buffer_width + x_tm; \ + \ + for(int k = 0; k < height_tm; k++) { \ + for(int i = 0; i < width_tm; i++) { \ + *datapi += 1; \ + datapi++; \ + } \ + datapi += buffer_width - width_tm; \ + } \ +} + +static void mark_buffer_area( unsigned char *data, + int x_tm, int y_tm, + int width_tm, int height_tm, + int buffer_width, int depth) { + + if ((depth == 24) || (depth == 32)) { + MARK_BUFFER_AREA_C( data, + x_tm, + y_tm, + width_tm, + height_tm, + buffer_width, + 32); + } else { + MARK_BUFFER_AREA_C( data, + x_tm, + y_tm, + width_tm, + height_tm, + buffer_width, + 16); + } +} + +//besides taking the first screenshot, this functions primary purpose is to +//initialize the structures and memory. +static int rmdFirstFrame(ProgData *pdata, Image *image) { + const XRectangle *rrect = &pdata->brwin.rrect; + + if (pdata->args.noshared) { + image->ximage = XGetImage( pdata->dpy, + pdata->specs.root, + rrect->x, + rrect->y, + rrect->width, + rrect->height, + AllPlanes, + ZPixmap); + } else { + image->ximage = XShmCreateImage(pdata->dpy, + pdata->specs.visual, + pdata->specs.depth, + ZPixmap, + NULL, + &image->shm_info, + rrect->width, + rrect->height); + + image->shm_info.shmid = shmget( IPC_PRIVATE, + image->ximage->bytes_per_line * + image->ximage->height, + IPC_CREAT|0777); + + if (image->shm_info.shmid == -1) { + fprintf(stderr, "Failed to obtain Shared Memory segment!\n"); + return 12; + } + + image->shm_info.shmaddr = image->ximage->data = shmat(image->shm_info.shmid, NULL, 0); + image->shm_info.readOnly = False; + shmctl(image->shm_info.shmid, IPC_RMID, NULL); + + if (!XShmAttach(pdata->dpy, &image->shm_info)) { + fprintf(stderr, "Failed to attach shared memory to proccess.\n"); + return 12; + } + + XShmGetImage( pdata->dpy, + pdata->specs.root, + image->ximage, + rrect->x, + rrect->y, + AllPlanes); + } + + rmdUpdateYuvBuffer( &pdata->enc_data->yuv, + (unsigned char *)image->ximage->data, + NULL, + 0, + 0, + rrect->width, + rrect->height, + pdata->args.no_quick_subsample, + pdata->specs.depth); + + return 0; +} + +//make a deep copy +static void rmdBRWinCpy(BRWindow *target, BRWindow *source) { + target->winrect = source->winrect; + target->rrect = source->rrect; + target->windowid = source->windowid; +} + +//recenters the capture area to the mouse +//without exiting the display bounding box +static void rmdMoveCaptureArea( XRectangle *rect, + int cursor_x, + int cursor_y, + int width, + int height) { + int t_x = 0, t_y = 0; + + t_x = cursor_x - rect->width / 2; + t_x = (t_x >> 1) << 1; + rect->x = (t_x < 0) ? 0 : ((t_x + rect->width > width) ? width - rect->width : t_x); + t_y = cursor_y - rect->height / 2; + t_y = (t_y >> 1) << 1; + rect->y = (t_y < 0 ) ? 0 : ((t_y + rect->height > height) ? height - rect->height : t_y); +} + + +static void rmdBlocksReset(unsigned int blocks_w, unsigned int blocks_h) { + memset(yblocks, 0, blocks_w * blocks_h * sizeof(*yblocks)); + memset(ublocks, 0, blocks_w * blocks_h * sizeof(*ublocks)); + memset(vblocks, 0, blocks_w * blocks_h * sizeof(*vblocks)); +} + + +/** +* Extract cache blocks from damage list +* +* \param root Root entry of the list with damaged areas +* +* \param x_offset left x of the recording area +* +* \param y_offset upper y of the recording area +* +* \param blocks_w Width of image in blocks +* +* \param blocks_h Height of image in blocks +*/ +static void rmdBlocksFromList( RectArea **root, + unsigned int x_offset, + unsigned int y_offset, + unsigned int blocks_w, + unsigned int blocks_h) { + + rmdBlocksReset(blocks_w, blocks_h); + + for (RectArea *temp = *root; temp; temp = temp->next) { + int row_start, row_end, column_start, column_end; + + column_start = ((int)(temp->rect.x - x_offset)) / Y_UNIT_WIDTH; + column_end = ((int)(temp->rect.x + (temp->rect.width - 1) - x_offset)) / Y_UNIT_WIDTH; + row_start = ((int)(temp->rect.y - y_offset)) / Y_UNIT_WIDTH; + row_end = ((int)(temp->rect.y + (temp->rect.height - 1) - y_offset)) / Y_UNIT_WIDTH; + + for (int i = row_start; i < row_end + 1; i++) { + for (int j = column_start; j < column_end + 1; j++) { + + int blockno = i * blocks_w + j; + + yblocks[blockno] = 1; + ublocks[blockno] = 1; + vblocks[blockno] = 1; + } + } + } +} + +void *rmdGetFrame(ProgData *pdata) { + int blocks_w = pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH, + blocks_h = pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH; + unsigned int msk_ret; + XRectangle mouse_pos_abs, mouse_pos_rel, mouse_pos_temp; + BRWindow temp_brwin; + Window root_ret, child_ret; //Frame + XFixesCursorImage *xcim = NULL; + Image image = {}, image_back = {}; //the image that holds + //the current full screenshot + int init_img1 = 0, init_img2 = 0, img_sel, d_buff; + + img_sel = d_buff = pdata->args.full_shots; + + if ((init_img1 = rmdFirstFrame(pdata, &image) != 0)) { + if (pdata->args.encOnTheFly) { + if (remove(pdata->args.filename)) { + perror("Error while removing file:\n"); + } else { + fprintf(stderr, "SIGABRT received, file %s removed\n", + pdata->args.filename); + } + } else { + rmdPurgeCache(pdata->cache_data, !pdata->args.nosound); + } + + exit(init_img1); + } + + if (d_buff) { + if ((init_img2 = rmdFirstFrame(pdata, &image_back) != 0)) { + if (pdata->args.encOnTheFly) { + if (remove(pdata->args.filename)) { + perror("Error while removing file:\n"); + } else{ + fprintf(stderr, "SIGABRT received, file %s removed\n", + pdata->args.filename); + } + } else { + rmdPurgeCache(pdata->cache_data, !pdata->args.nosound); + } + + exit(init_img2); + } + } + + if (!pdata->args.noframe) { + pdata->shaped_w = rmdFrameInit( pdata->dpy, + pdata->specs.screen, + pdata->specs.root, + pdata->brwin.rrect.x, + pdata->brwin.rrect.y, + pdata->brwin.rrect.width, + pdata->brwin.rrect.height); + + XSelectInput(pdata->dpy, pdata->shaped_w, ExposureMask); + } + + mouse_pos_abs.x = mouse_pos_temp.x = 0; + mouse_pos_abs.y = mouse_pos_temp.y = 0; + mouse_pos_abs.width = mouse_pos_temp.width = pdata->dummy_p_size; + mouse_pos_abs.height = mouse_pos_temp.height = pdata->dummy_p_size; + + //This is the the place where we call XSelectInput + //and arrange so that we listen for damage on all + //windows + rmdInitEventsPolling(pdata); + + while (pdata->running) { + unsigned time_frameno; + + //if we are left behind we must not wait. + //also before actually pausing we must make sure the streams + //are synced. sound stops so this should only happen quickly. + pthread_mutex_lock(&pdata->time_mutex); + while ( (pdata->avd > 0 || pdata->args.nosound) && + pdata->capture_frameno >= pdata->time_frameno) + pthread_cond_wait(&pdata->time_cond, &pdata->time_mutex); + + time_frameno = pdata->time_frameno; + pthread_mutex_unlock(&pdata->time_mutex); + + //read all events and construct list with damage + //events (if not full_shots) + rmdEventLoop(pdata); + + if (pdata->paused) + continue; + + //switch back and front buffers (full_shots only) + if (d_buff) + img_sel = img_sel ? 0 : 1; + + rmdBRWinCpy(&temp_brwin, &pdata->brwin); + + + if ( pdata->args.xfixes_cursor || + pdata->args.have_dummy_cursor || + pdata->args.follow_mouse) { + + + // Pointer sequence: + // * Mark previous position as dirty with rmdRectInsert() + // * Update to new position + // * Mark new position as dirty with rmdRectInsert() + if ( !pdata->args.full_shots && + mouse_pos_temp.x >= 0 && + mouse_pos_temp.y >= 0 && + mouse_pos_temp.width > 0 && + mouse_pos_temp.height > 0) { + rmdRectInsert(&pdata->rect_root, &mouse_pos_temp); + } + + if (pdata->args.xfixes_cursor) { + xcim = XFixesGetCursorImage(pdata->dpy); + mouse_pos_abs.x = xcim->x - xcim->xhot; + mouse_pos_abs.y = xcim->y - xcim->yhot; + mouse_pos_abs.width = xcim->width; + mouse_pos_abs.height = xcim->height; + } else { + XQueryPointer( pdata->dpy, + pdata->specs.root, + &root_ret, &child_ret, + (int *)&mouse_pos_abs.x, + (int *)&mouse_pos_abs.y, + (int *)&mouse_pos_rel.x, + (int *)&mouse_pos_rel.y, + &msk_ret); + } + + clip_dummy_pointer_area(&mouse_pos_abs, &temp_brwin.rrect, &mouse_pos_temp); + if ( mouse_pos_temp.x >= 0 && + mouse_pos_temp.y >= 0 && + mouse_pos_temp.width > 0 && + mouse_pos_temp.height > 0) { + + //there are 3 capture scenarios: + // * Xdamage + // * full-shots with double buffering + // * full-shots on a single buffer + //The last one cannot be reached through + //this code (see above how the d_buf variable is set), but + //even if it could, it would not be of interest regarding the + //marking of the cursor area. Single buffer means full repaint + //on every frame so there is no need for marking at all. + + if (!pdata->args.full_shots) { + rmdRectInsert(&pdata->rect_root, &mouse_pos_temp); + } else if (d_buff) { + unsigned char *back_buff= img_sel ? + ((unsigned char*)image.ximage->data) : + ((unsigned char*)image_back.ximage->data); + + mark_buffer_area( + back_buff, + mouse_pos_temp.x - temp_brwin.rrect.x, + mouse_pos_temp.y - temp_brwin.rrect.y, + mouse_pos_temp.width, mouse_pos_temp.height, temp_brwin.rrect.width, + pdata->specs.depth + ); + } + } + } + + if (pdata->args.follow_mouse) { + rmdMoveCaptureArea( &pdata->brwin.rrect, + mouse_pos_abs.x + pdata->args.xfixes_cursor ? xcim->xhot : 0, + mouse_pos_abs.y + pdata->args.xfixes_cursor ? xcim->yhot : 0, + pdata->specs.width, + pdata->specs.height); + + if (!pdata->args.noframe) + rmdMoveFrame( pdata->dpy, + pdata->shaped_w, + temp_brwin.rrect.x, + temp_brwin.rrect.y); + } + + if (!pdata->args.full_shots) { + pthread_mutex_lock(&pdata->yuv_mutex); + rmdUpdateImage( pdata->dpy, + &pdata->enc_data->yuv, + &pdata->specs, + &pdata->rect_root, + &temp_brwin, + pdata->enc_data, + &image, + pdata->args.noshared, + pdata->shm_opcode, + pdata->args.no_quick_subsample); + + rmdBlocksFromList( &pdata->rect_root, + temp_brwin.rrect.x, + temp_brwin.rrect.y, + blocks_w, + blocks_h); + + pthread_mutex_unlock(&pdata->yuv_mutex); + } else { + unsigned char *front_buff = !img_sel ? ((unsigned char*)image.ximage->data): + ((unsigned char*)image_back.ximage->data); + unsigned char *back_buff = !d_buff ? NULL : (img_sel ? + ((unsigned char*)image.ximage->data): + ((unsigned char*)image_back.ximage->data)); + + if (pdata->args.noshared) { + rmdGetZPixmap( pdata->dpy, + pdata->specs.root, + image.ximage->data, + temp_brwin.rrect.x, + temp_brwin.rrect.y, + temp_brwin.rrect.width, + temp_brwin.rrect.height); + } else { + XShmGetImage( pdata->dpy, + pdata->specs.root, + ((!img_sel) ? image.ximage : image_back.ximage), + temp_brwin.rrect.x, + temp_brwin.rrect.y, + AllPlanes); + } + + pthread_mutex_lock(&pdata->yuv_mutex); + rmdBlocksReset(blocks_w, blocks_h); + rmdUpdateYuvBuffer( &pdata->enc_data->yuv, + front_buff, + back_buff, + 0, + 0, + temp_brwin.rrect.width, + temp_brwin.rrect.height, + pdata->args.no_quick_subsample, + pdata->specs.depth); + + pthread_mutex_unlock(&pdata->yuv_mutex); + } + + if (pdata->args.xfixes_cursor || pdata->args.have_dummy_cursor) { + int mouse_xoffset, mouse_yoffset; + + //avoid segfaults + clip_dummy_pointer_area(&mouse_pos_abs, &temp_brwin.rrect, &mouse_pos_temp); + mouse_xoffset = mouse_pos_temp.x - mouse_pos_abs.x; + mouse_yoffset = mouse_pos_temp.y - mouse_pos_abs.y; + + if ((mouse_xoffset < 0) || (mouse_xoffset > mouse_pos_abs.width)) + mouse_xoffset = 0; + + if ((mouse_yoffset < 0) || (mouse_yoffset > mouse_pos_abs.height)) + mouse_yoffset = 0; + + //draw the cursor + if ( (mouse_pos_temp.x >= 0) && + (mouse_pos_temp.y >= 0) && + (mouse_pos_temp.width > 0) && + (mouse_pos_temp.height > 0)) { + + if (pdata->args.xfixes_cursor) { + rmdXFixesPointerToYuv( + &pdata->enc_data->yuv, + ((unsigned char*)xcim->pixels), + mouse_pos_temp.x - temp_brwin.rrect.x, + mouse_pos_temp.y - temp_brwin.rrect.y, + mouse_pos_temp.width, + mouse_pos_temp.height, + mouse_xoffset, + mouse_yoffset, + xcim->width-mouse_pos_temp.width + ); + } else { + rmdDummyPointerToYuv( + &pdata->enc_data->yuv, + pdata->dummy_pointer, + mouse_pos_temp.x - temp_brwin.rrect.x, + mouse_pos_temp.y - temp_brwin.rrect.y, + mouse_pos_temp.width, + mouse_pos_temp.height, + mouse_xoffset, + mouse_yoffset, + pdata->npxl + ); + } + + if (d_buff) { + //make previous cursor position dirty + //on the currently front buffer (which + //will be the back buffer next time it's + //used) + unsigned char *front_buff = !img_sel ? + ((unsigned char*)image.ximage->data) : + ((unsigned char*)image_back.ximage->data); + + mark_buffer_area( + front_buff, + mouse_pos_temp.x - temp_brwin.rrect.x, + mouse_pos_temp.y - temp_brwin.rrect.y, + mouse_pos_temp.width, + mouse_pos_temp.height, + temp_brwin.rrect.width, + pdata->specs.depth + ); + } + + } + + if (pdata->args.xfixes_cursor) { + XFree(xcim); + xcim = NULL; + } + } + + if (!pdata->args.full_shots) + rmdClearList(&pdata->rect_root); + + /* notify the encoder of the new frame */ + pthread_mutex_lock(&pdata->img_buff_ready_mutex); + pdata->capture_frameno = time_frameno; + pthread_cond_broadcast(&pdata->image_buffer_ready); + pthread_mutex_unlock(&pdata->img_buff_ready_mutex); + } + + if (!pdata->args.noframe) + XDestroyWindow(pdata->dpy, pdata->shaped_w); + + if (!pdata->args.noshared) { + XShmDetach(pdata->dpy, &image.shm_info); + shmdt(image.shm_info.shmaddr); + if (d_buff) { + XShmDetach(pdata->dpy, &image_back.shm_info); + shmdt(image_back.shm_info.shmaddr); + } + } + + pthread_exit(&errno); +} diff --git a/src/rmd_get_frame.h b/src/rmd_get_frame.h new file mode 100644 index 0000000..162c05e --- /dev/null +++ b/src/rmd_get_frame.h @@ -0,0 +1,41 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef GET_FRAME_H +#define GET_FRAME_H 1 + +#include "rmd_types.h" + + +/** +* Retrieve frame form xserver, and transform to a yuv buffer, +* either directly(full shots) or by calling UpdateImage. +* \param pdata ProgData struct containing all program data +*/ +void *rmdGetFrame(ProgData *pdata); + + +#endif diff --git a/src/rmd_getzpixmap.c b/src/rmd_getzpixmap.c new file mode 100644 index 0000000..43465c8 --- /dev/null +++ b/src/rmd_getzpixmap.c @@ -0,0 +1,117 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_getzpixmap.h" + +#include "rmd_types.h" + + +#include <X11/Xlib.h> +#include <X11/Xlibint.h> +#include <X11/extensions/shmproto.h> +#include <X11/extensions/XShm.h> + + + +int rmdGetZPixmap( Display *dpy, + Window root, + char *data, + int x, + int y, + int width, + int height) { + + xGetImageReply reply; + xGetImageReq *request; + long nbytes; + + LockDisplay(dpy); + GetReq(GetImage, request); + request->drawable = root; + request->x = x; + request->y = y; + request->width = width; + request->height = height; + request->planeMask = AllPlanes; + request->format = ZPixmap; + + if (!_XReply(dpy, (xReply *)&reply, 0, xFalse) || !reply.length) { + UnlockDisplay(dpy); + SyncHandle(); + + return 1; + } + + nbytes = (long)reply.length << 2; + _XReadPad(dpy, data, nbytes); + UnlockDisplay(dpy); + SyncHandle(); + + return 0; +} + +int rmdGetZPixmapSHM( Display *dpy, + Window root, + XShmSegmentInfo *shminfo, + int shm_opcode, + char *data, + int x, + int y, + int width, + int height) { + + xShmGetImageReply reply; + xShmGetImageReq *request = NULL; + + LockDisplay(dpy); + GetReq(ShmGetImage, request); + + request->reqType = shm_opcode; + request->shmReqType = X_ShmGetImage; + request->shmseg = shminfo->shmseg; + + request->drawable = root; + request->x = x; + request->y = y; + request->width = width; + request->height = height; + request->planeMask = AllPlanes; + request->format = ZPixmap; + request->offset = data - shminfo->shmaddr; + + if ((!_XReply(dpy, (xReply *)&reply, 0, xFalse)) || !reply.length) { + UnlockDisplay(dpy); + SyncHandle(); + + return 1; + } + + UnlockDisplay(dpy); + SyncHandle(); + + return 0; +} diff --git a/src/rmd_getzpixmap.h b/src/rmd_getzpixmap.h new file mode 100644 index 0000000..87ad7bb --- /dev/null +++ b/src/rmd_getzpixmap.h @@ -0,0 +1,99 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef GETZPIXMAP_H +#define GETZPIXMAP_H 1 + +#include "rmd_types.h" + +#include <X11/extensions/XShm.h> + +/** +* Rerieve pixmap data from xserver +* +* \param dpy Connection to the server +* +* \param root root window of the display +* +* \param data (preallocated)buffer to place the data +* +* \param x x position of the screenshot +* +* \param y y position of the screenshot +* +* \param x x position of the screenshot +* +* \param width width of the screenshot +* +* \param height height position of the screenshot +* +* \returns 0 on Success 1 on Failure +*/ +int rmdGetZPixmap(Display *dpy, + Window root, + char *data, + int x, + int y, + int width, + int height); + +/** +* Rerieve pixmap data from xserver through the MIT-Shm extension +* +* \param dpy Connection to the server +* +* \param root root window of the display +* +* \param shminfo Info for the shared memory segment +* +* \param shm_opcode Opcode of Shm extension +* +* \param data (preallocated)buffer to place the data +* +* \param x x position of the screenshot +* +* \param y y position of the screenshot +* +* \param x x position of the screenshot +* +* \param width width of the screenshot +* +* \param height height position of the screenshot +* +* \returns 0 on Success 1 on Failure +*/ +int rmdGetZPixmapSHM(Display *dpy, + Window root, + XShmSegmentInfo *shminfo, + int shm_opcode, + char *data, + int x, + int y, + int width, + int height); + + +#endif diff --git a/src/rmd_init_encoder.c b/src/rmd_init_encoder.c new file mode 100644 index 0000000..a0d81e6 --- /dev/null +++ b/src/rmd_init_encoder.c @@ -0,0 +1,346 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_init_encoder.h" + +#include "rmd_types.h" + +#include "skeleton.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + + + +static void m_add_fishead_packet(ogg_stream_state *m_ogg_state) { + fishead_packet skel_fp; + + skel_fp.ptime_n = skel_fp.btime_n = 0; + skel_fp.ptime_d = skel_fp.btime_d = 1000; + + add_fishead_to_stream(m_ogg_state, &skel_fp); +} + + +static int rmdIncrementalNaming(char **name) { + struct stat buff; + char *base_name__; + int i = 0, fname_length = strlen(*name)-4; + + base_name__ = malloc(fname_length+1); + strncpy(base_name__, *name, fname_length); + base_name__[fname_length] = '\0'; + + //this will go on an endless loop if you have 65536? + //files with the same name + //or it will crash and die.anyone interested in trying ? + while (stat(*name, &buff)==0) { + //create new name + char *tname = malloc(strlen(*name)+10); + char numbuf[8]; + + strcpy(tname, base_name__); + strcat(tname, "-"); + i++; + snprintf( numbuf, 8, "%d", i ); + strcat(tname, numbuf); + strcat(tname, ".ogv"); + //save new name + + free(*name); + *name = malloc(strlen(tname)+1); + strcpy(*name, tname); + free(tname); + } + + free(base_name__); + return 0; +} + +void rmdInitEncoder(ProgData *pdata, EncData *enc_data_t, int buffer_ready) { + int y0, y1, y2, fname_length; + ogg_stream_state m_ogg_skel; + ogg_page skel_og_pg; + fisbone_packet skel_fbv, //video fisbone packet + skel_fba; //audio fisbone packet + const char *fname; + + pdata->enc_data = enc_data_t; + + fname = pdata->args.filename; + fname_length = strlen(fname); + if (!(fname_length>4 && !strcasecmp(&fname[fname_length-3], "ogv"))) { + + char *new_name = malloc(fname_length + 5); + strcpy(new_name, fname); + strcat(new_name, ".ogv"); + + free(pdata->args.filename); + pdata->args.filename = new_name; + } + + if (!pdata->args.overwrite) { + rmdIncrementalNaming(&(pdata)->args.filename); + fprintf(stderr, "Output file: %s\n", pdata->args.filename); + } + + enc_data_t->fp = fopen((pdata)->args.filename, "w"); + if (enc_data_t->fp == NULL) { + fprintf(stderr, "Cannot open file %s for writting!\n", (pdata)->args.filename); + exit(13); + } + + //each stream must have a unique + srand(time(NULL)); + y0 = rand() + 1; + y1 = rand() + 1; + y2 = rand() + 1; + y2 += (y1 == y2); + y0 = (((y0 == y1) || (y0 == y2)) ? (y1 + y2) : y0); + + //init ogg streams + //skeleton first + ogg_stream_init(&m_ogg_skel, y0); + m_add_fishead_packet(&m_ogg_skel); + if (ogg_stream_pageout(&m_ogg_skel, &skel_og_pg) != 1) { + fprintf (stderr, "Internal Ogg library error.\n"); + exit (2); + } + fwrite(skel_og_pg.header, 1, skel_og_pg.header_len, enc_data_t->fp); + fwrite(skel_og_pg.body, 1, skel_og_pg.body_len, enc_data_t->fp); + + ogg_stream_init(&enc_data_t->m_ogg_ts, y1); + if (!pdata->args.nosound) + ogg_stream_init(&enc_data_t->m_ogg_vs, y2); + + theora_info_init(&enc_data_t->m_th_inf); + enc_data_t->m_th_inf.frame_width = pdata->brwin.rrect.width; + enc_data_t->m_th_inf.frame_height = pdata->brwin.rrect.height; + enc_data_t->m_th_inf.width = ((enc_data_t->m_th_inf.frame_width + 15) >> 4) << 4; + enc_data_t->m_th_inf.height = ((enc_data_t->m_th_inf.frame_height + 15) >> 4) << 4; + enc_data_t->m_th_inf.offset_x = 0; + enc_data_t->m_th_inf.offset_y = 0; + + enc_data_t->m_th_inf.fps_numerator = pdata->args.fps * 100.0; + enc_data_t->m_th_inf.fps_denominator = 100; + enc_data_t->m_th_inf.aspect_numerator = 1; + enc_data_t->m_th_inf.aspect_denominator = 1; + + enc_data_t->m_th_inf.colorspace = OC_CS_UNSPECIFIED; + enc_data_t->m_th_inf.pixelformat = OC_PF_420; + + enc_data_t->m_th_inf.target_bitrate = pdata->args.v_bitrate; + enc_data_t->m_th_inf.quality = pdata->args.v_quality; + enc_data_t->m_th_inf.dropframes_p = 0; + enc_data_t->m_th_inf.quick_p = 1; + enc_data_t->m_th_inf.keyframe_auto_p = 1; + enc_data_t->m_th_inf.keyframe_frequency = 64; + enc_data_t->m_th_inf.keyframe_frequency_force = 64; + enc_data_t->m_th_inf.keyframe_data_target_bitrate = enc_data_t->m_th_inf.quality * 1.5; + enc_data_t->m_th_inf.keyframe_auto_threshold = 80; + enc_data_t->m_th_inf.keyframe_mindistance = 8; + enc_data_t->m_th_inf.noise_sensitivity = 1; + enc_data_t->m_th_inf.sharpness = 2; + + if (!pdata->args.nosound) { + int ret; + vorbis_info_init(&enc_data_t->m_vo_inf); + ret = vorbis_encode_init_vbr( &enc_data_t->m_vo_inf, + pdata->args.channels, + pdata->args.frequency, + (float)pdata->args.s_quality*0.1); + if (ret) { + fprintf(stderr, "Error while setting up vorbis stream quality!\n"); + exit(2); + } + vorbis_comment_init(&enc_data_t->m_vo_cmmnt); + vorbis_analysis_init(&enc_data_t->m_vo_dsp, &enc_data_t->m_vo_inf); + vorbis_block_init(&enc_data_t->m_vo_dsp, &enc_data_t->m_vo_block); + } + + if (theora_encode_init(&enc_data_t->m_th_st, &enc_data_t->m_th_inf)) { + fprintf(stderr, "Theora encoder initialization failure.\n"); + exit(2); + } + + if (theora_encode_header(&enc_data_t->m_th_st, &enc_data_t->m_ogg_pckt1)) { + fprintf(stderr, "Theora enocde header failure.\n"); + exit(2); + } + + ogg_stream_packetin(&enc_data_t->m_ogg_ts, &enc_data_t->m_ogg_pckt1); + if (ogg_stream_pageout(&enc_data_t->m_ogg_ts, &enc_data_t->m_ogg_pg) != 1) { + fprintf(stderr, "Internal Ogg library error.\n"); + exit(2); + } + fwrite(enc_data_t->m_ogg_pg.header, 1, + enc_data_t->m_ogg_pg.header_len, + enc_data_t->fp); + fwrite(enc_data_t->m_ogg_pg.body, 1, + enc_data_t->m_ogg_pg.body_len, + enc_data_t->fp); + + theora_comment_init(&enc_data_t->m_th_cmmnt); + theora_comment_add_tag(&enc_data_t->m_th_cmmnt, "recordMyDesktop", VERSION); + theora_encode_comment(&enc_data_t->m_th_cmmnt, &enc_data_t->m_ogg_pckt1); + ogg_stream_packetin(&enc_data_t->m_ogg_ts, &enc_data_t->m_ogg_pckt1); + theora_encode_tables(&enc_data_t->m_th_st, &enc_data_t->m_ogg_pckt1); + ogg_stream_packetin(&enc_data_t->m_ogg_ts, &enc_data_t->m_ogg_pckt1); + + if (!pdata->args.nosound) { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout( &enc_data_t->m_vo_dsp, + &enc_data_t->m_vo_cmmnt, + &header, &header_comm, + &header_code); + ogg_stream_packetin(&enc_data_t->m_ogg_vs, &header); + if (ogg_stream_pageout(&enc_data_t->m_ogg_vs, &enc_data_t->m_ogg_pg) != 1) { + fprintf(stderr, "Internal Ogg library error.\n"); + exit(2); + } + + fwrite(enc_data_t->m_ogg_pg.header, 1, + enc_data_t->m_ogg_pg.header_len, + enc_data_t->fp); + + fwrite(enc_data_t->m_ogg_pg.body, 1, + enc_data_t->m_ogg_pg.body_len, + enc_data_t->fp); + + ogg_stream_packetin(&enc_data_t->m_ogg_vs, &header_comm); + ogg_stream_packetin(&enc_data_t->m_ogg_vs, &header_code); + } + + //fishbone packets go here + memset(&skel_fbv, 0, sizeof(skel_fbv)); + skel_fbv.serial_no = enc_data_t->m_ogg_ts.serialno; + skel_fbv.nr_header_packet = 3; + skel_fbv.granule_rate_n = enc_data_t->m_th_inf.fps_numerator; + skel_fbv.granule_rate_d = enc_data_t->m_th_inf.fps_denominator; + skel_fbv.start_granule = 0; + skel_fbv.preroll = 0; + skel_fbv.granule_shift = theora_granule_shift(&enc_data_t->m_th_inf); + add_message_header_field(&skel_fbv, "Content-Type", "video/theora"); + + add_fisbone_to_stream(&m_ogg_skel, &skel_fbv); + + if (!pdata->args.nosound) { + + memset(&skel_fba, 0, sizeof(skel_fba)); + skel_fba.serial_no = enc_data_t->m_ogg_vs.serialno; + skel_fba.nr_header_packet = 3; + skel_fba.granule_rate_n = pdata->args.frequency; + skel_fba.granule_rate_d = (ogg_int64_t)1; + skel_fba.start_granule = 0; + skel_fba.preroll = 2; + skel_fba.granule_shift = 0; + add_message_header_field(&skel_fba, "Content-Type", "audio/vorbis"); + + add_fisbone_to_stream(&m_ogg_skel, &skel_fba); + } + + while (1) { + int result = ogg_stream_flush(&m_ogg_skel, &skel_og_pg); + if (result < 0) { + fprintf (stderr, "Internal Ogg library error.\n"); + exit(2); + } + + if (result == 0) + break; + + fwrite(skel_og_pg.header, 1, skel_og_pg.header_len, enc_data_t->fp); + fwrite(skel_og_pg.body, 1, skel_og_pg.body_len, enc_data_t->fp); + } + + while (1) { + int result = ogg_stream_flush(&enc_data_t->m_ogg_ts, &enc_data_t->m_ogg_pg); + if (result < 0) { + fprintf(stderr, "Internal Ogg library error.\n"); + exit(2); + } + + if (result == 0) + break; + + fwrite( enc_data_t->m_ogg_pg.header, 1, + enc_data_t->m_ogg_pg.header_len, + enc_data_t->fp); + fwrite( enc_data_t->m_ogg_pg.body, 1, + enc_data_t->m_ogg_pg.body_len, + enc_data_t->fp); + } + + if (!pdata->args.nosound) { + while (1) { + int result = ogg_stream_flush(&enc_data_t->m_ogg_vs, &enc_data_t->m_ogg_pg); + if (result < 0) { + fprintf(stderr, "Internal Ogg library error.\n"); + exit(2); + } + + if (result == 0) + break; + + fwrite( enc_data_t->m_ogg_pg.header, 1, + enc_data_t->m_ogg_pg.header_len, + enc_data_t->fp); + fwrite( enc_data_t->m_ogg_pg.body, 1, + enc_data_t->m_ogg_pg.body_len, + enc_data_t->fp); + } + } + + //skeleton eos + add_eos_packet_to_stream(&m_ogg_skel); + if (ogg_stream_flush(&m_ogg_skel, &skel_og_pg) < 0) { + fprintf(stderr, "Internal Ogg library error.\n"); + exit(2); + } + fwrite(skel_og_pg.header, 1, skel_og_pg.header_len, enc_data_t->fp); + + //theora buffer allocation, if any + if (!buffer_ready) { + enc_data_t->yuv.y = malloc(enc_data_t->m_th_inf.height * enc_data_t->m_th_inf.width); + enc_data_t->yuv.u = malloc(enc_data_t->m_th_inf.height * enc_data_t->m_th_inf.width / 4); + enc_data_t->yuv.v = malloc(enc_data_t->m_th_inf.height * enc_data_t->m_th_inf.width / 4); + enc_data_t->yuv.y_width = enc_data_t->m_th_inf.width; + enc_data_t->yuv.y_height = enc_data_t->m_th_inf.height; + enc_data_t->yuv.y_stride = enc_data_t->m_th_inf.width; + + enc_data_t->yuv.uv_width = enc_data_t->m_th_inf.width / 2; + enc_data_t->yuv.uv_height = enc_data_t->m_th_inf.height / 2; + enc_data_t->yuv.uv_stride = enc_data_t->m_th_inf.width / 2; + } + + theora_info_clear(&enc_data_t->m_th_inf); +} diff --git a/src/rmd_init_encoder.h b/src/rmd_init_encoder.h new file mode 100644 index 0000000..bbf73c7 --- /dev/null +++ b/src/rmd_init_encoder.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef INIT_ENCODER_H +#define INIT_ENCODER_H 1 + +#include "rmd_types.h" + + +/** +* Initialize theora,vorbis encoders, and their respective ogg streams. +* +* \param pdata ProgData struct containing all program data +* +* \param enc_data_t Encoding options +* +* \param buffer_ready when 1, the yuv buffer must be preallocated +* when 0 rmdInitEncoder will alocate a new one +* +*/ +void rmdInitEncoder(ProgData *pdata,EncData *enc_data_t,int buffer_ready); + + +#endif diff --git a/src/rmd_initialize_data.c b/src/rmd_initialize_data.c new file mode 100644 index 0000000..0f0a073 --- /dev/null +++ b/src/rmd_initialize_data.c @@ -0,0 +1,252 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_initialize_data.h" + +#include "rmd_cache.h" +#include "rmd_init_encoder.h" +#include "rmd_jack.h" +#include "rmd_make_dummy_pointer.h" +#include "rmd_opendev.h" +#include "rmd_yuv_utils.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_LIBASOUND +static void rmdFixBufferSize(snd_pcm_uframes_t *buffsize) { + snd_pcm_uframes_t buffsize_t = *buffsize, +#else +static void rmdFixBufferSize(u_int32_t *buffsize) { + u_int32_t buffsize_t = *buffsize, +#endif + buffsize_ret = 1; + + while (buffsize_t > 1) { + buffsize_t >>= 1; + buffsize_ret <<= 1; + } + fprintf(stderr, "Buffer size adjusted to %d from %d frames.\n", + (int)buffsize_ret, (int)*buffsize); +} + +int rmdInitializeData(ProgData *pdata, EncData *enc_data, CacheData *cache_data) { + fprintf(stderr, "Initializing...\n"); + + rmdMakeMatrices(); + if (pdata->args.have_dummy_cursor) { + pdata->dummy_pointer = rmdMakeDummyPointer( pdata->dpy, + &pdata->specs, + 16, + pdata->args.cursor_color, + 0, + &pdata->npxl); + pdata->dummy_p_size = 16; + } else + pdata->dummy_p_size = 0; + + pthread_mutex_init(&pdata->sound_buffer_mutex, NULL); + pthread_mutex_init(&pdata->img_buff_ready_mutex, NULL); + pthread_mutex_init(&pdata->theora_lib_mutex, NULL); + pthread_mutex_init(&pdata->vorbis_lib_mutex, NULL); + pthread_mutex_init(&pdata->libogg_mutex, NULL); + pthread_mutex_init(&pdata->yuv_mutex, NULL); + pthread_mutex_init(&pdata->pause_mutex, NULL); + pthread_mutex_init(&pdata->time_mutex, NULL); + pthread_cond_init(&pdata->time_cond, NULL); + pthread_cond_init(&pdata->pause_cond, NULL); + pthread_cond_init(&pdata->image_buffer_ready, NULL); + pthread_cond_init(&pdata->sound_data_read, NULL); + pthread_cond_init(&pdata->theora_lib_clean, NULL); + pthread_cond_init(&pdata->vorbis_lib_clean, NULL); + + pdata->th_encoding_clean = pdata->v_encoding_clean = 1; + pdata->rect_root = NULL; + pdata->avd = 0; + pdata->sound_buffer = NULL; + pdata->running = TRUE; + pdata->paused = FALSE; + pdata->aborted = FALSE; + pdata->pause_state_changed = FALSE; + pdata->frames_total = 0; + pdata->frames_lost = 0; + pdata->capture_frameno = 0; + pdata->time_frameno = 0; + + if (!pdata->args.nosound) { + if (!pdata->args.use_jack) { + rmdFixBufferSize(&pdata->args.buffsize); +#ifdef HAVE_LIBASOUND + pdata->sound_handle=rmdOpenDev( pdata->args.device, + &pdata->args.channels, + &pdata->args.frequency, + &pdata->args.buffsize, + &pdata->periodsize, + &pdata->periodtime, + &pdata->hard_pause); + + pdata->sound_framesize=(snd_pcm_format_width(SND_PCM_FORMAT_S16_LE) / 8 * + pdata->args.channels); + + if (pdata->sound_handle == NULL) { +#else + pdata->sound_handle = rmdOpenDev( pdata->args.device, + pdata->args.channels, + pdata->args.frequency); + + pdata->periodtime = (1000000 * pdata->args.buffsize) / + ((pdata->args.channels<<1) * pdata->args.frequency); + //when using OSS periodsize serves as an alias of buffsize + pdata->periodsize = pdata->args.buffsize; + pdata->sound_framesize = pdata->args.channels<<1; + if (pdata->sound_handle<0) { +#endif + fprintf(stderr, "Error while opening/configuring soundcard %s\n" + "Try running with the --no-sound or specify a " + "correct device.\n", + pdata->args.device); + return 3; + } + } else { +#ifdef HAVE_LIBJACK + int jack_error = 0; + pdata->jdata->port_names = pdata->args.jack_port_names; + pdata->jdata->nports = pdata->args.jack_nports; + pdata->jdata->ringbuffer_secs = pdata->args.jack_ringbuffer_secs; + pdata->jdata->sound_buffer_mutex = &pdata->sound_buffer_mutex; + pdata->jdata->sound_data_read = &pdata->sound_data_read; + pdata->jdata->capture_started = 0; + + if ((jack_error = rmdStartJackClient(pdata->jdata))!=0) + return jack_error; + + pdata->args.buffsize = pdata->jdata->buffersize; + pdata->periodsize = pdata->args.buffsize; + pdata->args.frequency = pdata->jdata->frequency; + pdata->args.channels = pdata->jdata->nports; + pdata->periodtime = 1000000 * pdata->args.buffsize / + pdata->args.frequency; + pdata->sound_framesize = sizeof(jack_default_audio_sample_t) * + pdata->jdata->nports; +#else + fprintf(stderr, "Should not be here!\n"); + exit(-1); +#endif + } + } + + if (pdata->args.encOnTheFly) + rmdInitEncoder(pdata, enc_data, 0); + else + rmdInitCacheData(pdata, enc_data, cache_data); + + for (int i = 0; i < pdata->enc_data->yuv.y_width * pdata->enc_data->yuv.y_height; i++) + pdata->enc_data->yuv.y[i] = 0; + + for (int i = 0; i < pdata->enc_data->yuv.uv_width * pdata->enc_data->yuv.uv_height; i++) + pdata->enc_data->yuv.v[i] = pdata->enc_data->yuv.u[i] = 127; + + yblocks = malloc((pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH) * + (pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH)); + ublocks = malloc((pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH) * + (pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH)); + vblocks = malloc((pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH) * + (pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH)); + + pdata->frametime = 1000000 / pdata->args.fps; + + return 0; + +} + +void rmdSetupDefaultArgs(ProgArgs *args) { + args->delay = 0; + args->windowid = 0; + args->x = 0; + args->y = 0; + args->width = 0; + args->height = 0; + args->rescue_path = NULL; + args->nosound = 0; + args->full_shots = 0; + args->follow_mouse = 0; + args->encOnTheFly = 0; + args->nowmcheck = 0; + args->overwrite = 0; + args->use_jack = 0; + args->noshared = 0; + args->no_encode = 0; + args->noframe = 0; + args->jack_nports = 0; + args->jack_ringbuffer_secs = 3.0; + args->zerocompression = 1; + args->no_quick_subsample = 1; + args->cursor_color = 1; + args->have_dummy_cursor = 0; + args->xfixes_cursor = 1; + args->fps = 15; + args->channels = 1; + args->frequency = 22050; + args->buffsize = 4096; + args->v_bitrate = 0; + args->v_quality = 63; + args->s_quality = 10; + + if (getenv("DISPLAY") != NULL) { + args->display = malloc(strlen(getenv("DISPLAY")) + 1); + strcpy(args->display, getenv("DISPLAY")); + } else { + args->display = NULL; + } + + memset(args->jack_port_names, 0, sizeof(args->jack_port_names)); + + args->device = malloc(strlen(DEFAULT_AUDIO_DEVICE) + 1); + strcpy(args->device, DEFAULT_AUDIO_DEVICE); + + args->workdir = malloc(5); + strcpy(args->workdir, "/tmp"); + + args->pause_shortcut = malloc(15); + strcpy(args->pause_shortcut, "Control+Mod1+p"); + + args->stop_shortcut = malloc(15); + strcpy(args->stop_shortcut, "Control+Mod1+s"); + + args->filename = malloc(8); + strcpy(args->filename, "out.ogv"); +} + +void rmdCleanUp(void) { + free(yblocks); + free(ublocks); + free(vblocks); +} diff --git a/src/rmd_initialize_data.h b/src/rmd_initialize_data.h new file mode 100644 index 0000000..a5e43dd --- /dev/null +++ b/src/rmd_initialize_data.h @@ -0,0 +1,62 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef INITIALIZE_DATA_H +#define INITIALIZE_DATA_H 1 + +#include "rmd_types.h" + + +/** +* initialize lists,mutexes,image buffers, take first screenshot, +* and anything else needed before launching the capture threads. +* +* \param pdata ProgData struct containing all program data +* +* \param enc_data reference to enc_data structure +* +* \param cache_data reference to cache_data structure +* +* \returns 0 on success, other values must cause the program to exit +*/ +int rmdInitializeData(ProgData *pdata, + EncData *enc_data, + CacheData *cache_data); + +/** +* Sets up the ProgArgs structure to default values. +*/ +void rmdSetupDefaultArgs(ProgArgs *args); + +/** +* Currently only frees some memory +* (y,u,v blocks) +* +*/ +void rmdCleanUp(void); + + +#endif diff --git a/src/rmd_jack.c b/src/rmd_jack.c new file mode 100644 index 0000000..841017f --- /dev/null +++ b/src/rmd_jack.c @@ -0,0 +1,209 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_jack.h" + +#include "rmd_types.h" + +#include <pthread.h> + +#include <string.h> + +#ifdef HAVE_LIBJACK + + +/** +* Callback for capture through jack +* +* \param nframes Number of frames captured +* +* \param jdata_t Pointer to JackData struct containing port +* and client information +* +* \returns Zero always +*/ +static int rmdJackCapture(jack_nframes_t nframes, void *jdata_t) { + JackData *jdata = (JackData *)jdata_t; + + if (!jdata->pdata->running || jdata->pdata->paused || !jdata->capture_started) + return 0; + + for(int i = 0; i < jdata->nports; i++) + jdata->portbuf[i] = jack_port_get_buffer(jdata->ports[i], nframes); + + + pthread_mutex_lock(jdata->sound_buffer_mutex); +//vorbis analysis buffer wants uninterleaved data +//so we are simply placing the buffers for every channel +//sequentially on the ringbuffer + for(int i = 0; i < jdata->nports; i++) + jack_ringbuffer_write( jdata->sound_buffer, + (void *)(jdata->portbuf[i]), + nframes * + sizeof(jack_default_audio_sample_t)); + + pthread_cond_signal(jdata->sound_data_read); + pthread_mutex_unlock(jdata->sound_buffer_mutex); + + return 0; +} + +/** +* Register and Activate specified ports +* +* \param jdata_t Pointer to JackData struct containing port +* and client information +* +* \returns 0 on Success, 1 on failure +*/ +static int rmdSetupPorts(JackData *jdata) { + jdata->ports = malloc(sizeof(jack_port_t *) * jdata->nports); + jdata->portbuf = malloc(sizeof(jack_default_audio_sample_t *) * jdata->nports); + memset(jdata->portbuf, 0, sizeof(jack_default_audio_sample_t *) * jdata->nports); + + for(int i = 0; i < jdata->nports; i++) { + char name[64];//recordMyDesktop:input_n<64 is enough for full name + char num[8]; + strcpy(name, "input_"); + snprintf(num, 8, "%d", i + 1); + strcat(name, num); + + jdata->ports[i] = jack_port_register( jdata->client, + name, + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, + 0); + + if (!jdata->ports[i]) { + fprintf(stderr, "Cannot register input port \"%s\"!\n", name); + return 1; + } + + if (jack_connect(jdata->client, jdata->port_names[i], jack_port_name(jdata->ports[i]))) { + + fprintf(stderr, "Cannot connect input port %s to %s\n", + jack_port_name(jdata->ports[i]), + jdata->port_names[i]); + return 1; + } + } + return 0; +} + +//in case the jack server shuts down +//the program should stop recording, +//encode the result(if not on the fly) +//an exit cleanly. +static void rmdJackShutdown(void *jdata_t) { + JackData *jdata = (JackData *)jdata_t; + + jdata->pdata->running = FALSE; + + fprintf (stderr, "JACK shutdown\n"); +} + +int rmdStartJackClient(JackData *jdata) { + float ring_buffer_size = 0.0; + int pid; + char pidbuf[8]; + char rmd_client_name[32]; + + //construct the jack client name + //which is recordMyDesktop-pid + //in order to allow multiple + //instances of recordMyDesktop + //to connetc to a Jack Server + strcpy(rmd_client_name, "recordMyDesktop-"); + pid = getpid(); + snprintf( pidbuf, 8, "%d", pid ); + strcat(rmd_client_name, pidbuf); + + if ((jdata->client = jack_client_new(rmd_client_name)) == 0) { + fprintf(stderr, "Could not create new client!\n" + "Make sure that Jack server is running!\n"); + return 15; + } +//in contrast to ALSA and OSS, Jack dictates frequency +//and buffersize to the values it was launched with. +//Supposedly jack_set_buffer_size can set the buffersize +//but that causes some kind of halt and keeps giving me +//zero buffers. +//recordMyDesktop cannot handle buffer size changes. +//FIXME +//There is a callback for buffer size changes that I should use. +//It will provide clean exits, instead of ?segfaults? . +//Continuing though is not possible, with the current layout +//(it might be in some cases, but it will certainly be the cause +//of unpredicted problems). A clean exit is preferable +//and any recording up to that point will be encoded and saved. + jdata->frequency = jack_get_sample_rate(jdata->client); + jdata->buffersize = jack_get_buffer_size(jdata->client); + ring_buffer_size = ( jdata->ringbuffer_secs* + jdata->frequency* + sizeof(jack_default_audio_sample_t)* + jdata->nports); + jdata->sound_buffer = jack_ringbuffer_create((int)(ring_buffer_size+0.5));//round up + jack_set_process_callback(jdata->client, rmdJackCapture, jdata); + jack_on_shutdown(jdata->client, rmdJackShutdown, jdata); + + if (jack_activate(jdata->client)) { + fprintf(stderr, "cannot activate client!\n"); + return 16; + } + + if (rmdSetupPorts(jdata)) { + jack_client_close(jdata->client); + return 17; + } + + return 0; +} + +int rmdStopJackClient(JackData *jdata) { + int ret = 0; + + jack_ringbuffer_free(jdata->sound_buffer); + if (jack_client_close(jdata->client)) { + fprintf(stderr, "Cannot close Jack client!\n"); + ret = 1; + } + +/*TODO*/ +//I need to make some kind of program/thread +//flow diagram to see where it's safe to dlclose +//because here it causes a segfault. + +// if (dlclose(jdata->jack_lib_handle)) { +// fprintf(stderr, "Cannot unload Jack library!\n"); +// ret=1; +// } +// else fprintf(stderr, "Unloaded Jack library.\n"); + + return ret; +} + +#endif diff --git a/src/rmd_jack.h b/src/rmd_jack.h new file mode 100644 index 0000000..a216c1b --- /dev/null +++ b/src/rmd_jack.h @@ -0,0 +1,59 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_JACK_H +#define RMD_JACK_H 1 + +#include "rmd_types.h" + + +#ifdef HAVE_LIBJACK + +/** +* create and activate client,register ports +* +* \param jdata_t Pointer to JackData struct containing port +* and client information +* +* \returns 0 on Success, error code on failure +* (depending on where the error occured) +*/ +int rmdStartJackClient(JackData *jdata); + +/** +* Close Jack Client +* +* \param jdata_t Pointer to JackData struct containing port +* and client information +* +* \returns 0 on Success, 1 on failure +*/ +int rmdStopJackClient(JackData *jdata); + +#endif + + +#endif diff --git a/src/rmd_load_cache.c b/src/rmd_load_cache.c new file mode 100644 index 0000000..8ca37f6 --- /dev/null +++ b/src/rmd_load_cache.c @@ -0,0 +1,336 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_load_cache.h" + +#include "rmd_cache.h" +#include "rmd_encode_image_buffer.h" +#include "rmd_encode_sound_buffer.h" +#include "rmd_macro.h" +#include "rmd_types.h" + +#include <pthread.h> +#include <signal.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +//The number of bytes for every +//sub-block of the y,u and v planes. +//Since the blocks are square +//these are obviously the squares +//of the widths(specified above), +//but the definitions bellow are only +//for convenience anyway. +#define Y_UNIT_BYTES 0x0100 +#define UV_UNIT_BYTES 0x0040 + + +//The frame after retrieval. +//Based on the Header information +//we can read the correct amount of bytes. +typedef struct _CachedFrame{ + FrameHeader *header; + u_int32_t *YBlocks, //identifying number on the grid, + *UBlocks, //starting at top left + *VBlocks; // >> >> + unsigned char *YData, //pointer to data for the blocks that have changed, + *UData, //which have to be remapped + *VData; //on the buffer when reading +}CachedFrame; + + +static void rmdLoadBlock( + unsigned char *dest, + unsigned char *source, + int blockno, + int width, + int height, + int blockwidth) { + + int block_i = blockno / (width / blockwidth),//place on the grid + block_k = blockno % (width / blockwidth); + + for (int j = 0; j < blockwidth; j++)//we copy rows + memcpy( &dest[(block_i * width + block_k) * blockwidth + j * width], + &source[j * blockwidth], + blockwidth); +} + +//returns number of bytes +static int rmdReadZF(void * buffer, size_t size, size_t nmemb, FILE *ucfp, gzFile ifp) { + if ((ifp != NULL && ucfp != NULL) || + (ifp == NULL && ucfp == NULL)) + return -1; + else if (ucfp != NULL) { + return size * fread(buffer, size, nmemb, ucfp); + } else + return gzread(ifp, buffer, size * nmemb); +} + +static int rmdReadFrame(CachedFrame *frame, FILE *ucfp, gzFile ifp) { + int index_entry_size = sizeof(u_int32_t); + + if (frame->header->Ynum > 0) { + if (rmdReadZF( frame->YBlocks, + index_entry_size, + frame->header->Ynum, + ucfp, + ifp) != index_entry_size * frame->header->Ynum) { + return -1; + } + } + + if (frame->header->Unum > 0) { + if (rmdReadZF( frame->UBlocks, + index_entry_size, + frame->header->Unum, + ucfp, + ifp) != index_entry_size * frame->header->Unum) { + return -1; + } + } + + if (frame->header->Vnum > 0) { + if (rmdReadZF( frame->VBlocks, + index_entry_size, + frame->header->Vnum, + ucfp, + ifp) != index_entry_size * frame->header->Vnum) { + return -1; + } + } + + if (frame->header->Ynum > 0) { + if (rmdReadZF( frame->YData, + Y_UNIT_BYTES, + frame->header->Ynum, + ucfp, + ifp) != Y_UNIT_BYTES * frame->header->Ynum) { + return -2; + } + } + + if (frame->header->Unum > 0) { + if (rmdReadZF( frame->UData, + UV_UNIT_BYTES, + frame->header->Unum, + ucfp, + ifp) != UV_UNIT_BYTES * frame->header->Unum) { + return -2; + } + } + + if (frame->header->Vnum > 0) { + if (rmdReadZF( frame->VData, + UV_UNIT_BYTES, + frame->header->Vnum, + ucfp, + ifp) != UV_UNIT_BYTES * frame->header->Vnum) { + return -2; + } + } + + return 0; +} + +void *rmdLoadCache(ProgData *pdata) { + + yuv_buffer *yuv = &pdata->enc_data->yuv; + gzFile ifp = NULL; + FILE *ucfp = NULL; + FILE *afp = pdata->cache_data->afp; + FrameHeader fheader; + CachedFrame frame; + int nth_cache = 1, + audio_end = 0, + extra_frames = 0, //total number of duplicated frames + missing_frames = 0, //if this is found >0 current run will not load + //a frame but it will proccess the previous + thread_exit = 0, //0 success, -1 couldn't find files,1 couldn't remove + blocknum_x = pdata->enc_data->yuv.y_width / Y_UNIT_WIDTH, + blocknum_y = pdata->enc_data->yuv.y_height / Y_UNIT_WIDTH, + blockszy = Y_UNIT_BYTES,//size of y plane block in bytes + blockszuv = UV_UNIT_BYTES;//size of u,v plane blocks in bytes + signed char *sound_data = (signed char *)malloc(pdata->periodsize * pdata->sound_framesize); + + u_int32_t YBlocks[(yuv->y_width * yuv->y_height) / Y_UNIT_BYTES], + UBlocks[(yuv->uv_width * yuv->uv_height) / UV_UNIT_BYTES], + VBlocks[(yuv->uv_width * yuv->uv_height) / UV_UNIT_BYTES]; + + // We allocate the frame that we will use + frame.header = &fheader; + frame.YBlocks = YBlocks; + frame.UBlocks = UBlocks; + frame.VBlocks = VBlocks; + frame.YData = malloc(yuv->y_width * yuv->y_height); + frame.UData = malloc(yuv->uv_width * yuv->uv_height); + frame.VData = malloc(yuv->uv_width * yuv->uv_height); + + //and the we open our files + if (!pdata->args.zerocompression) { + ifp = gzopen(pdata->cache_data->imgdata, "rb"); + if (ifp == NULL) { + thread_exit = -1; + pthread_exit(&thread_exit); + } + } else { + ucfp = fopen(pdata->cache_data->imgdata, "rb"); + if (ucfp == NULL) { + thread_exit = -1; + pthread_exit(&thread_exit); + } + } + + if (!pdata->args.nosound) { + afp = fopen(pdata->cache_data->audiodata, "rb"); + if (afp == NULL) { + thread_exit = -1; + pthread_exit(&thread_exit); + } + } + + //this will be used now to define if we proccess audio or video + //on any given loop. + pdata->avd = 0; + //If sound finishes first,we go on with the video. + //If video ends we will do one more run to flush audio in the ogg file + while (pdata->running) { + //video load and encoding + if (pdata->avd <= 0 || pdata->args.nosound || audio_end) { + if (missing_frames > 0) { + extra_frames++; + missing_frames--; + rmdSyncEncodeImageBuffer(pdata); + } else if (((!pdata->args.zerocompression) && + (gzread(ifp, frame.header, sizeof(FrameHeader)) == + sizeof(FrameHeader) )) || + ((pdata->args.zerocompression) && + (fread(frame.header, sizeof(FrameHeader), 1, ucfp) == 1))) { + //sync + missing_frames += frame.header->current_total - + (extra_frames + frame.header->frameno); + if (pdata->frames_total) { + fprintf(stdout, "\r[%d%%] ", + ((frame.header->frameno + extra_frames) * 100) / pdata->frames_total); + } else + fprintf(stdout, "\r[%d frames rendered] ", + (frame.header->frameno + extra_frames)); + fflush(stdout); + if ( (frame.header->Ynum <= blocknum_x * blocknum_y) && + (frame.header->Unum <= blocknum_x * blocknum_y) && + (frame.header->Vnum <= blocknum_x * blocknum_y) && + !rmdReadFrame( &frame, + (pdata->args.zerocompression ? ucfp : NULL), + (pdata->args.zerocompression ? NULL : ifp)) + ) { + + //load the blocks for each buffer + if (frame.header->Ynum) + for (int j = 0; j < frame.header->Ynum; j++) + rmdLoadBlock( yuv->y, + &frame.YData[j * blockszy], + frame.YBlocks[j], + yuv->y_width, + yuv->y_height, + Y_UNIT_WIDTH); + if (frame.header->Unum) + for (int j = 0; j < frame.header->Unum; j++) + rmdLoadBlock( yuv->u, + &frame.UData[j * blockszuv], + frame.UBlocks[j], + yuv->uv_width, + yuv->uv_height, + UV_UNIT_WIDTH); + if (frame.header->Vnum) + for (int j = 0; j < frame.header->Vnum; j++) + rmdLoadBlock( yuv->v, + &frame.VData[j * blockszuv], + frame.VBlocks[j], + yuv->uv_width, + yuv->uv_height, + UV_UNIT_WIDTH); + + //encode. This is not made in a thread since + //now blocking is not a problem + //and this way sync problems + //can be avoided more easily. + rmdSyncEncodeImageBuffer(pdata); + } else { + raise(SIGINT); + continue; + } + } else { + if (rmdSwapCacheFilesRead( pdata->cache_data->imgdata, + nth_cache, + &ifp, + &ucfp)) { + raise(SIGINT); + } else { + fprintf(stderr, "\t[Cache File %d]", nth_cache); + nth_cache++; + } + continue; + } + //audio load and encoding + } else { + if (!audio_end) { + int nbytes = fread(sound_data, 1, pdata->periodsize* + pdata->sound_framesize, afp); + if (nbytes <= 0) + audio_end = 1; + else + rmdSyncEncodeSoundBuffer(pdata, sound_data); + } + } + } + + pthread_mutex_lock(&pdata->theora_lib_mutex); + pdata->th_encoding_clean = 1; + pthread_cond_signal(&pdata->theora_lib_clean); + pthread_mutex_unlock(&pdata->theora_lib_mutex); + + pthread_mutex_lock(&pdata->vorbis_lib_mutex); + pdata->v_encoding_clean = 1; + pthread_cond_signal(&pdata->vorbis_lib_clean); + pthread_mutex_unlock(&pdata->vorbis_lib_mutex); + fprintf(stdout, "\n"); + + // Clear frame + free(frame.YData); + free(frame.UData); + free(frame.VData); + + free(sound_data); + + if (!pdata->args.nosound) + fclose(afp); + + pthread_exit(&thread_exit); +} diff --git a/src/rmd_load_cache.h b/src/rmd_load_cache.h new file mode 100644 index 0000000..917f31f --- /dev/null +++ b/src/rmd_load_cache.h @@ -0,0 +1,42 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef LOAD_CACHE +#define LOAD_CACHE 1 + +#include "rmd_types.h" + + +/** +* Cache loading and processing thread +* +* \param pdata ProgData struct containing all program data +* +*/ +void *rmdLoadCache(ProgData *pdata); + + +#endif diff --git a/src/rmd_macro.h b/src/rmd_macro.h new file mode 100644 index 0000000..2f1ac88 --- /dev/null +++ b/src/rmd_macro.h @@ -0,0 +1,83 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMDMACRO_H +#define RMDMACRO_H 1 + +#include "config.h" + +#include "rmd_types.h" + +#include <limits.h> + + +//define which way we are reading a pixmap +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define __ABYTE 3 +#define __RBYTE 2 +#define __GBYTE 1 +#define __BBYTE 0 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +#define __ABYTE 0 +#define __RBYTE 1 +#define __GBYTE 2 +#define __BBYTE 3 + +#else +#error Only little-endian and big-endian systems are supported +#endif + +#define __RVALUE_32(tmp_val) (((tmp_val)&0x00ff0000)>>16) +#define __GVALUE_32(tmp_val) (((tmp_val)&0x0000ff00)>>8) +#define __BVALUE_32(tmp_val) (((tmp_val)&0x000000ff)) + +#define __R16_MASK 0xf800 +#define __G16_MASK 0x7e0 +#define __B16_MASK 0x1f + +#define __RVALUE_16(tmp_val) ((((tmp_val)&__R16_MASK)>>11)*8) +#define __GVALUE_16(tmp_val) ((((tmp_val)&__G16_MASK)>>5)*4) +#define __BVALUE_16(tmp_val) ((((tmp_val)&__B16_MASK))*8) + +//xfixes pointer data are written as unsigned long +//(even though the server returns CARD32) +//so we need to set the step accordingly to +//avoid problems (amd64 has 8byte ulong) +#define RMD_ULONG_SIZE_T (sizeof(unsigned long)) + +//The width, in bytes, of the blocks +//on which the y,u and v planes are broken. +//These blocks are square. +#define Y_UNIT_WIDTH 0x0010 +#define UV_UNIT_WIDTH 0x0008 + +#define MAX(a, b) ((a) < (b)) ? (b) : (a) +#define MIN(a, b) ((a) > (b)) ? (b) : (a) + +#endif + diff --git a/src/rmd_make_dummy_pointer.c b/src/rmd_make_dummy_pointer.c new file mode 100644 index 0000000..25bb027 --- /dev/null +++ b/src/rmd_make_dummy_pointer.c @@ -0,0 +1,94 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_make_dummy_pointer.h" + +#include "rmd_macro.h" +#include "rmd_types.h" + +#include <X11/Xlib.h> +#include <stdlib.h> + +unsigned char *rmdMakeDummyPointer( Display *display, + DisplaySpecs *specs, + int size, + int color, + int type, + unsigned char *npxl) { + + unsigned long bpixel = XBlackPixel(display, specs->screen); + unsigned long wpixel = XWhitePixel(display, specs->screen); + int i, k, o = '.'; + unsigned long b = color ? 'w' : 'b', w = color ? 'b' : 'w'; + char pmask[1][16][16] = {{ + {w,w,o,o,o,o,o,o,o,o,o,o,o,o,o,o}, + {w,b,w,o,o,o,o,o,o,o,o,o,o,o,o,o}, + {w,b,b,w,o,o,o,o,o,o,o,o,o,o,o,o}, + {w,b,b,b,w,o,o,o,o,o,o,o,o,o,o,o}, + {w,b,b,b,b,w,o,o,o,o,o,o,o,o,o,o}, + {w,b,b,b,b,b,w,o,o,o,o,o,o,o,o,o}, + {w,b,b,b,b,b,b,w,o,o,o,o,o,o,o,o}, + {w,b,b,b,b,b,b,b,w,o,o,o,o,o,o,o}, + {w,b,b,b,b,b,b,b,b,w,o,o,o,o,o,o}, + {w,b,b,b,b,b,w,w,w,w,o,o,o,o,o,o}, + {w,b,b,b,b,b,w,o,o,o,o,o,o,o,o,o}, + {w,b,b,w,w,b,b,w,o,o,o,o,o,o,o,o}, + {w,b,w,o,w,b,b,w,o,o,o,o,o,o,o,o}, + {w,w,o,o,o,w,b,b,w,o,o,o,o,o,o,o}, + {o,o,o,o,o,w,b,b,w,o,o,o,o,o,o,o}, + {o,o,o,o,o,o,w,w,o,o,o,o,o,o,o,o}} + }; + unsigned char *ret = malloc(size * sizeof(char[size * 4])); + unsigned char wp[4] = { + (wpixel ^ 0xff000000) >> 24, + (wpixel ^ 0x00ff0000) >> 16, + (wpixel ^ 0x0000ff00) >> 8, + (wpixel ^ 0x000000ff) + }; + unsigned char bp[4] = { + (bpixel ^ 0xff000000) >> 24, + (bpixel ^ 0x00ff0000) >> 16, + (bpixel ^ 0x0000ff00) >> 8, + (bpixel ^ 0x000000ff) + }; + + *npxl = ((wp[0] - 1) != bp[0]) ? wp[0] - 100 : wp[0] - 102; + for (i = 0; i < size; i++) { + for (k = 0; k < size; k++) { + ret[(i * size + k) * 4 + __ABYTE] = (pmask[type][i][k] == 'w') ? + wp[0]:(pmask[type][i][k] == 'b') ? bp[0] : *npxl; + ret[(i * size + k) * 4 + __RBYTE] = (pmask[type][i][k] == 'w') ? + wp[1]:(pmask[type][i][k] == 'b') ? bp[1] : *npxl; + ret[(i * size + k) * 4 + __GBYTE] = (pmask[type][i][k] == 'w') ? + wp[2]:(pmask[type][i][k] == 'b') ? bp[2] : *npxl; + ret[(i * size + k) * 4 + __BBYTE] = (pmask[type][i][k] == 'w') ? + wp[3]:(pmask[type][i][k] == 'b') ? bp[3] : *npxl; + } + } + + return ret; +} diff --git a/src/rmd_make_dummy_pointer.h b/src/rmd_make_dummy_pointer.h new file mode 100644 index 0000000..3eccd42 --- /dev/null +++ b/src/rmd_make_dummy_pointer.h @@ -0,0 +1,59 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef MAKE_DUMMY_POINTER_H +#define MAKE_DUMMY_POINTER_H 1 + +#include "rmd_types.h" + + +/** +* Create an array containing the data for the dummy pointer +* +* \param specs DisplaySpecs struct with +* information about the display to be recorded +* +* \param size Pointer size, always square, always 16.(exists only +* for the possibility to create more dummy cursors) +* \param color 0 white, 1 black +* +* \param type Always 0.(exists only for the possibility to create +* more dummy cursors) +* +* \param npxl Return of pixel value that denotes non-drawing, while +* applying the cursor on the target image +* +* \returns Pointer to pixel data of the cursor +*/ +unsigned char *rmdMakeDummyPointer(Display *display, + DisplaySpecs *specs, + int size, + int color, + int type, + unsigned char *npxl); + + +#endif diff --git a/src/rmd_math.c b/src/rmd_math.c new file mode 100644 index 0000000..1cf3d29 --- /dev/null +++ b/src/rmd_math.c @@ -0,0 +1,33 @@ +/****************************************************************************** +* recordMyDesktop - rmd_math.c * +******************************************************************************* +* * +* Copyright (C) 2008 John Varouhakis * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_math.h" + +#include <math.h> + +double rmdRoundf(double val) { + return val < 0.0 ? ceilf(val - .5) : floorf(val + 0.5); +} diff --git a/src/rmd_math.h b/src/rmd_math.h new file mode 100644 index 0000000..87c6166 --- /dev/null +++ b/src/rmd_math.h @@ -0,0 +1,44 @@ +/****************************************************************************** +* recordMyDesktop - rmd_math.h * +******************************************************************************* +* * +* Copyright (C) 2008 John Varouhakis * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_MATH_H +#define RMD_MATH_H 1 + +/* + * Since roundf depends on C99, using it might make + * the code non-portable. rmdRoundf solves this + * problem, by behaving identically with roundf + * and being portable (floorf and ceilf, that are + * used in the implementation, are defined in C89) + * + * \param val Number to be rounded + * + * \returns val rounded + * + */ +double rmdRoundf( double val ); + +#endif + diff --git a/src/rmd_opendev.c b/src/rmd_opendev.c new file mode 100644 index 0000000..37fb33c --- /dev/null +++ b/src/rmd_opendev.c @@ -0,0 +1,189 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_opendev.h" + +#ifdef HAVE_LIBASOUND + #include <alsa/asoundlib.h> +#else + #include <sys/ioctl.h> + #include <sys/soundcard.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + + +#ifdef HAVE_LIBASOUND + +snd_pcm_t *rmdOpenDev( const char *pcm_dev, + unsigned int *channels, + unsigned int *frequency, + snd_pcm_uframes_t *buffsize, + snd_pcm_uframes_t *periodsize, + unsigned int *periodtime, + int *hard_pause) { + + snd_pcm_t *mhandle; + snd_pcm_hw_params_t *hwparams; + unsigned int periods = 2; + unsigned int exactrate = *frequency; + + // The compiler might warn us because the expansion starts with + // assert(&hwparams) + snd_pcm_hw_params_alloca(&hwparams); + + if (snd_pcm_open(&mhandle, pcm_dev, SND_PCM_STREAM_CAPTURE, SND_PCM_ASYNC) < 0) { + fprintf(stderr, "Couldn't open PCM device %s\n", pcm_dev); + return NULL; + } else + fprintf(stderr, "Opened PCM device %s\n", pcm_dev); + + if (snd_pcm_hw_params_any(mhandle, hwparams) < 0) { + fprintf(stderr, "Couldn't configure PCM device.\n"); + return NULL; + } + + if (snd_pcm_hw_params_set_access(mhandle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) { + fprintf(stderr, "Couldn't set access.\n"); + return NULL; + } + + if (snd_pcm_hw_params_set_format(mhandle, hwparams, SND_PCM_FORMAT_S16_LE) < 0) { + fprintf(stderr, "Couldn't set format.\n"); + return NULL; + } + + if (snd_pcm_hw_params_set_rate_near(mhandle, hwparams, &exactrate, 0) < 0) { + fprintf(stderr, "Couldn't set frequency.\n"); + return NULL; + } + + if (*frequency != exactrate) { + fprintf(stderr, "Playback frequency %dHz is not available...\n" + "Using %dHz instead.\n", + *frequency, exactrate); + + *frequency = exactrate; + } + + if (snd_pcm_hw_params_set_channels_near(mhandle, hwparams, channels) < 0) { + fprintf(stderr, "Couldn't set channels number.\n"); + return NULL; + } + + if (*channels>2) { + fprintf(stderr, "Channels number should be 1(mono) or 2(stereo).\n"); + return NULL; + } + + if (snd_pcm_hw_params_set_periods_near(mhandle, hwparams, &periods, 0) < 0) { + fprintf(stderr, "Couldn't set periods.\n"); + return NULL; + } + + if (snd_pcm_hw_params_set_buffer_size_near(mhandle, hwparams, buffsize) < 0) { + fprintf(stderr, "Couldn't set buffer size.\n"); + return NULL; + } + + if (snd_pcm_hw_params(mhandle, hwparams) < 0) { + fprintf(stderr, "Couldn't set hardware parameters.\n"); + return NULL; + } + + if (hard_pause != NULL) { + if (!snd_pcm_hw_params_can_pause(hwparams)) + *hard_pause = 1; + } + + if (periodsize != NULL) + snd_pcm_hw_params_get_period_size(hwparams, periodsize, 0); + + if (periodtime != NULL) + snd_pcm_hw_params_get_period_time(hwparams, periodtime, 0); + + fprintf(stderr, + "Recording on device %s is set to:\n%d channels at %dHz\n", + pcm_dev, *channels, *frequency); + + snd_pcm_prepare(mhandle); + + return mhandle; +} + +#else + +int rmdOpenDev(const char *pcm_dev, unsigned int channels, unsigned int frequency) { + int fd ; + unsigned int value; + + fd = open(pcm_dev, O_RDONLY); + if (fd < 0) + return -1; + + if (ioctl(fd, SNDCTL_DSP_GETFMTS, &value) < 0) { + fprintf(stderr, "Couldn't get audio format list\n"); + return -1; + } + + if (value & AFMT_S16_LE) { + value = AFMT_S16_LE; + } else if (value & AFMT_S16_BE) { + value = AFMT_S16_BE; + } else { + fprintf(stderr, "Soundcard doesn't support signed 16-bit-data\n"); + return -1; + } + + if (ioctl(fd, SNDCTL_DSP_SETFMT, &value) < 0) { + fprintf(stderr, "Couldn't set audio format\n" ); + return -1; + } + + value = channels; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &value) < 0) { + fprintf(stderr, "Cannot set the number of channels\n" ); + return -1; + } + + value = frequency; + if (ioctl(fd, SNDCTL_DSP_SPEED, &value) < 0) { + fprintf(stderr, "Couldn't set audio frequency\n" ); + return -1; + } + + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK) < 0) { + fprintf(stderr, "Couldn't set audio blocking mode\n" ); + return -1; + } + + return fd; +} +#endif diff --git a/src/rmd_opendev.h b/src/rmd_opendev.h new file mode 100644 index 0000000..06dfc64 --- /dev/null +++ b/src/rmd_opendev.h @@ -0,0 +1,84 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef OPENDEV_H +#define OPENDEV_H 1 + +#include "rmd_types.h" + + +#ifdef HAVE_LIBASOUND +/** +* Try to open (alsa) sound device, with the desired parameters, +* and place the obtained ones on their place +* +* \param pcm_dev name of the device +* +* \param channels desired number of channels +* (gets modified with the acieved value) +* +* \param frequency desired frequency(gets modified with the acieved value) +* +* \param buffsize Size of buffer +* +* \param periodsize Size of a period(can be NULL) +* +* \param periodtime Duration of a period(can be NULL) +* +* \param hardpause Set to 1 when the device has to be stopped during pause +* and to 0 when it supports pausing +* (can be NULL) +* +* \returns snd_pcm_t handle on success, NULL on failure +*/ +snd_pcm_t *rmdOpenDev(const char *pcm_dev, + unsigned int *channels, + unsigned int *frequency, + snd_pcm_uframes_t *buffsize, + snd_pcm_uframes_t *periodsize, + unsigned int *periodtime, + int *hardpause); +#else +/** +* Try to open (OSS) sound device, with the desired parameters. +* +* +* \param pcm_dev name of the device +* +* \param channels desired number of channels +* +* \param frequency desired frequency +* +* +* \returns file descriptor of open device,-1 on failure +*/ +int rmdOpenDev(const char *pcm_dev, + unsigned int channels, + unsigned int frequency); +#endif + + +#endif diff --git a/src/rmd_parseargs.c b/src/rmd_parseargs.c new file mode 100644 index 0000000..12d2cd4 --- /dev/null +++ b/src/rmd_parseargs.c @@ -0,0 +1,511 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* Copyright (C) 2009 Martin Nordholts * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_parseargs.h" + +#include "rmd_types.h" + +#include <popt.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +#ifdef HAVE_LIBJACK +#define RMD_LIBJACK_STATUS "Enabled" +#define RMD_USE_JACK_EXTRA_FLAG 0 +#else +#define RMD_LIBJACK_STATUS "Disabled" +#define RMD_USE_JACK_EXTRA_FLAG POPT_ARGFLAG_DOC_HIDDEN +#endif + +#ifdef HAVE_LIBASOUND +#define RMD_LIBASOUND_STATUS "ALSA" +#else +#define RMD_LIBASOUND_STATUS "OSS" +#endif + +#define RMD_ARG_DELAY 1 +#define RMD_ARG_DUMMY_CURSOR 2 +#define RMD_ARG_USE_JACK 3 + +#define RMD_OPTION_TABLE(name, table) \ + { NULL, '\0', \ + POPT_ARG_INCLUDE_TABLE, (table), \ + 0, (name), NULL } + + +static void rmdPrintAndExit(poptContext con, enum poptCallbackReason reason, const struct poptOption *opt, const char *arg, const void *data); +static boolean rmdValidateArguments(const ProgArgs *args); + + +boolean rmdParseArgs(int argc, char **argv, ProgArgs *arg_return) { + poptContext popt_context = NULL; + boolean no_cursor = FALSE; + boolean quick_subsampling = FALSE; + boolean compress_cache = FALSE; + boolean success = TRUE; + int arg_id = 0; + + // Setup the option tables + struct poptOption generic_options[] = { + { NULL, '\0', + POPT_ARG_CALLBACK, (void *)rmdPrintAndExit, 0, + NULL, NULL }, + + { "help", 'h', + POPT_ARG_NONE, NULL, 0, + "Print this help and exit.", + NULL }, + + { "version", '\0', + POPT_ARG_NONE, NULL, 0, + "Print program version and exit.", + NULL }, + + { "print-config", '\0', + POPT_ARG_NONE, NULL, 0, + "Print info about options selected during compilation and exit.", + NULL }, + + POPT_TABLEEND }; + + struct poptOption image_options[] = { + { "windowid", '\0', + POPT_ARG_INT, &arg_return->windowid, 0, + "id of window to be recorded.", + "id_of_window" }, + + { "display", '\0', + POPT_ARG_STRING, &arg_return->display, 0, + "Display to connect to.", + "DISPLAY" }, + + { "x", 'x', + POPT_ARG_INT, &arg_return->x, 0, + "Offset in x direction.", + "N>=0" }, + + { "y", 'y', + POPT_ARG_INT, &arg_return->y, 0, + "Offset in y direction.", + "N>=0" }, + + { "width", '\0', + POPT_ARG_INT, &arg_return->width, 0, + "Width of recorded window.", + "N>0" }, + + { "height", '\0', + POPT_ARG_INT, &arg_return->height, 0, + "Height of recorded window.", + "N>0" }, + + { "dummy-cursor", '\0', + POPT_ARG_STRING, NULL, RMD_ARG_DUMMY_CURSOR, + "Color of the dummy cursor [black|white]", + "color" }, + + { "no-cursor", '\0', + POPT_ARG_NONE, &no_cursor, 0, + "Disable drawing of the cursor.", + NULL }, + + { "no-shared", '\0', + POPT_ARG_NONE, &arg_return->noshared, 0, + "Disable usage of MIT-shared memory extension(Not Recommended!).", + NULL }, + + { "full-shots", '\0', + POPT_ARG_NONE, &arg_return->full_shots, 0, + "Take full screenshot at every frame(Not recomended!).", + NULL }, + + { "follow-mouse", '\0', + POPT_ARG_NONE, &arg_return->follow_mouse, 0, + "Makes the capture area follow the mouse cursor. Autoenables --full-shots.", + NULL }, + + { "quick-subsampling", '\0', + POPT_ARG_NONE, &quick_subsampling, 0, + "Do subsampling of the chroma planes by discarding, not averaging.", + NULL }, + + { "fps", '\0', + POPT_ARG_FLOAT, &arg_return->fps, 0, + "A positive number denoting desired framerate.", + "N(number>0.0)" }, + + POPT_TABLEEND }; + + struct poptOption sound_options[] = { + { "channels", '\0', + POPT_ARG_INT, &arg_return->channels, 0, + "A positive number denoting desired sound channels in recording.", + "N" }, + + { "freq", '\0', + POPT_ARG_INT, &arg_return->frequency, 0, + "A positive number denoting desired sound frequency.", + "N" }, + + { "buffer-size", '\0', + POPT_ARG_INT, &arg_return->buffsize, 0, + "A positive number denoting the desired sound buffer size (in frames, when using ALSA or OSS)", + "N" }, + + { "ring-buffer-size", '\0', + POPT_ARG_FLOAT, &arg_return->jack_ringbuffer_secs, 0, + "A float number denoting the desired ring buffer size (in seconds, when using JACK only).", + "N" }, + + { "device", '\0', + POPT_ARG_STRING, &arg_return->device, 0, + "Sound device(default " DEFAULT_AUDIO_DEVICE ").", + "SOUND_DEVICE" }, + + { "use-jack", '\0', + POPT_ARG_STRING | RMD_USE_JACK_EXTRA_FLAG, &arg_return->x, RMD_ARG_USE_JACK, + "Record audio from the specified list of space-separated jack ports.", + "port1 port2... portn" }, + + { "no-sound", '\0', + POPT_ARG_NONE, &arg_return->nosound, 0, + "Do not record sound.", + NULL }, + + POPT_TABLEEND }; + + struct poptOption encoding_options[] = { + { "on-the-fly-encoding", '\0', + POPT_ARG_NONE, &arg_return->encOnTheFly, 0, + "Encode the audio-video data, while recording.", + NULL }, + + { "v_quality", '\0', + POPT_ARG_INT, &arg_return->v_quality, 0, + "A number from 0 to 63 for desired encoded video quality(default 63).", + "n" }, + + { "v_bitrate", '\0', + POPT_ARG_INT, &arg_return->v_bitrate, 0, + "A number from 0 to 2000000 for desired encoded video bitrate(default 0).", + "n" }, + + { "s_quality", '\0', + POPT_ARG_INT, &arg_return->s_quality, 0, + "Desired audio quality(-1 to 10).", + "n" }, + + POPT_TABLEEND }; + + struct poptOption misc_options[] = { + { "rescue", '\0', + POPT_ARG_STRING, &arg_return->rescue_path, 0, + "Encode data from a previous, crashed, session.", + "path_to_data" }, + + { "no-encode", '\0', + POPT_ARG_NONE | POPT_ARGFLAG_DOC_HIDDEN, &arg_return->no_encode, 0, + "Do not encode any data after recording is complete. This is instead done manually afterwards with --rescue.", + NULL }, + + { "no-wm-check", '\0', + POPT_ARG_NONE, &arg_return->nowmcheck, 0, + "Do not try to detect the window manager(and set options according to it)", + NULL }, + + { "no-frame", '\0', + POPT_ARG_NONE, &arg_return->noframe, 0, + "Don not show the frame that visualizes the recorded area.", + NULL }, + + { "pause-shortcut", '\0', + POPT_ARG_STRING, &arg_return->pause_shortcut, 0, + "Shortcut that will be used for (un)pausing (default Control+Mod1+p).", + "MOD+KEY" }, + + { "stop-shortcut", '\0', + POPT_ARG_STRING, &arg_return->stop_shortcut, 0, + "Shortcut that will be used to stop the recording (default Control+Mod1+s).", + "MOD+KEY" }, + + { "compress-cache", '\0', + POPT_ARG_NONE, &compress_cache, 0, + "Image data are cached with light compression.", + NULL }, + + { "workdir", '\0', + POPT_ARG_STRING, &arg_return->workdir, 0, + "Location where a temporary directory will be created to hold project files(default $HOME).", + "DIR" }, + + { "delay", '\0', + POPT_ARG_STRING, &arg_return->delay, RMD_ARG_DELAY, + "Number of secs(default), minutes or hours before capture starts(number can be float)", + "n[H|h|M|m]" }, + + { "overwrite", '\0', + POPT_ARG_NONE, &arg_return->overwrite, 0, + "If there is already a file with the same name, delete it (default is to add a number postfix to the new one).", + NULL }, + + { "output", 'o', + POPT_ARG_STRING, &arg_return->filename, 0, + "Name of recorded video(default out.ogv).", + "filename" }, + + POPT_TABLEEND }; + + struct poptOption rmd_args[] = { + RMD_OPTION_TABLE("Generic Options", generic_options), + RMD_OPTION_TABLE("Image Options", image_options), + RMD_OPTION_TABLE("Sound Options", sound_options), + RMD_OPTION_TABLE("Encoding Options", encoding_options), + RMD_OPTION_TABLE("Misc Options", misc_options), + POPT_TABLEEND }; + + + // Some trivial setup + popt_context = poptGetContext(NULL, argc, (const char **)argv, rmd_args, 0); + poptSetOtherOptionHelp(popt_context, "[OPTIONS]^filename"); + + + // Parse the arguments + while ((arg_id = poptGetNextOpt(popt_context)) > 0) { + char *arg = poptGetOptArg(popt_context); + + // Most arguments are handled completely by libpopt but we + // handle some by ourself, namely those in this switch case + switch (arg_id) { + case RMD_ARG_DELAY: + { + float num = atof(arg); + + if (num > 0.0) { + int k; + for (k = 0; k < strlen(arg); k++) { + if ((arg[k] == 'M') || (arg[k] == 'm')) { + num *= 60.0; + break; + } + else if ((arg[k] == 'H') || (arg[k] == 'h')) { + num *= 3600.0; + break; + } + } + arg_return->delay = (int) num; + } else { + fprintf(stderr, + "Argument Usage: --delay n[H|h|M|m]\n" + "where n is a float number\n"); + success = FALSE; + } + } + break; + + case RMD_ARG_DUMMY_CURSOR: + { + if (!strcmp(arg, "white")) + arg_return->cursor_color = 0; + else if (!strcmp(arg, "black")) + arg_return->cursor_color = 1; + else { + fprintf(stderr, + "Argument Usage:\n" + " --dummy-cursor [black|white]\n"); + success = FALSE; + } + arg_return->have_dummy_cursor = TRUE; + arg_return->xfixes_cursor = FALSE; + } + break; + + case RMD_ARG_USE_JACK: + { + arg_return->jack_nports = 0; + + while (arg) { + + arg_return->jack_nports++; + + arg_return->jack_port_names[arg_return->jack_nports - 1] = malloc(strlen(arg) + 1); + strcpy(arg_return->jack_port_names[arg_return->jack_nports - 1], arg); + + arg = poptGetOptArg(popt_context); + } + + if (arg_return->jack_nports > 0) { + arg_return->use_jack = 1; + } else { + fprintf(stderr, + "Argument Usage: --use-jack port1 port2... portn\n"); + success = FALSE; + } + + if (RMD_USE_JACK_EXTRA_FLAG == POPT_ARGFLAG_DOC_HIDDEN) { + fprintf(stderr, + "Warning, will ignore --use-jack flags, no libjack support in build.\n"); + } + } + + default: + break; + } + } + + if (arg_id == -1) { + // Parsing is complete, perform final adjustments + if (no_cursor) + arg_return->xfixes_cursor = FALSE; + + if (quick_subsampling) + arg_return->no_quick_subsample = FALSE; + + if (compress_cache) + arg_return->zerocompression = FALSE; + + if (arg_return->follow_mouse) + arg_return->full_shots = TRUE; + + if (!arg_return->filename) + arg_return->filename = strdup(poptGetArg(popt_context)); + } else { + // Parsing error, say what went wrong + const char *str = poptBadOption(popt_context, 0); + + success = FALSE; + + fprintf(stderr, + "Error when parsing `%s': ", str); + + switch (arg_id) { + case POPT_ERROR_NOARG: + fprintf(stderr, "Missing argument\n"); + break; + + case POPT_ERROR_BADNUMBER: + fprintf(stderr, "Bad number\n"); + break; + + default: + fprintf(stderr, "libpopt error: %d\n", arg_id); + break; + } + } + + // Make sure argument ranges are valid + success = success && rmdValidateArguments(arg_return); + + // Clean up + poptFreeContext(popt_context); + + return success; +} + +static boolean rmdValidateArguments(const ProgArgs *args) +{ + boolean success = TRUE; + + if (args->x < 0) { + fprintf(stdout, "-x must not be less than 0.\n"); + success = FALSE; + } + if (args->y < 0) { + fprintf(stdout, "-y must not be less than 0.\n"); + success = FALSE; + } + if (args->width < 0) { + fprintf(stdout, "--width must be larger than 0.\n"); + success = FALSE; + } + if (args->height < 0) { + fprintf(stdout, "--height must be larger than 0.\n"); + success = FALSE; + } + if (args->fps <= 0) { + fprintf(stdout, "--fps must be larger than 0.\n"); + success = FALSE; + } + if (args->v_quality < 0 || args->v_quality > 63) { + fprintf(stdout, "--v_quality must be within the inclusive range [0-63].\n"); + success = FALSE; + } + if (args->v_bitrate < 0 || args->v_quality > 2000000) { + fprintf(stdout, "--v_bitrate must be within the inclusive range [0-2000000].\n"); + success = FALSE; + } + if (args->frequency <= 0) { + fprintf(stdout, "--frequency must be larger than 0.\n"); + success = FALSE; + } + if (args->channels <= 0) { + fprintf(stdout, "--channels must be larger than 0.\n"); + success = FALSE; + } + if (args->buffsize <= 0) { + fprintf(stdout, "--buffer-size must be larger than 0.\n"); + success = FALSE; + } + if (args->jack_ringbuffer_secs <= 0) { + fprintf(stdout, "--jack-buffer-size must be larger than 0.\n"); + success = FALSE; + } + + return success; +} + +static void rmdPrintAndExit( poptContext con, + enum poptCallbackReason reason, + const struct poptOption *opt, + const char *arg, + const void *data) +{ + if (strcmp(opt->longName, "version") == 0) { + fprintf(stderr, "recordMyDesktop v%s\n\n", VERSION); + } else if (strcmp(opt->longName, "help") == 0) { + poptPrintHelp(con, stdout, 0); + fprintf(stdout, + "\n" + "\tIf no other options are specified, filename can be given without the -o switch.\n" + "\n" + "\n"); + } else if (strcmp(opt->longName, "print-config") == 0) { + fprintf(stdout, + "\n" + "recordMyDesktop was compiled with the following options:\n" + "\n" + "Jack:\t\t\t" RMD_LIBJACK_STATUS "\n" + "Default Audio Backend:\t" RMD_LIBASOUND_STATUS "\n" + "\n" + "\n"); + } + + exit(0); +} diff --git a/src/rmd_parseargs.h b/src/rmd_parseargs.h new file mode 100644 index 0000000..42f2bac --- /dev/null +++ b/src/rmd_parseargs.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef PARSEARGS_H +#define PARSEARGS_H 1 + +#include "rmd_types.h" + + +/** +* Fill ProgArgs struct with arguments entered at execution +* +* \param argc argc as entered from main +* +* \param argv argv as entered from main +* +* \param arg_return ProgArgs struct to be filled with the options +* +* \returns 0 on Success 1 on Failure +*/ +boolean rmdParseArgs(int argc, char **argv, ProgArgs *arg_return); + + +#endif diff --git a/src/rmd_poll_events.c b/src/rmd_poll_events.c new file mode 100644 index 0000000..49f2478 --- /dev/null +++ b/src/rmd_poll_events.c @@ -0,0 +1,245 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_poll_events.h" + +#include "rmd_frame.h" +#include "rmd_macro.h" +#include "rmd_rectinsert.h" +#include "rmd_types.h" + +#include <X11/Xlib.h> +#include <X11/Xlibint.h> +#include <X11/extensions/Xdamage.h> + +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <pthread.h> + + +static int clip_event_area(XDamageNotifyEvent *e, XRectangle *cliprect, XRectangle *res) +{ + +#if 0 + printf("got area %x,%x %x,%x\n", + e->area.x, + e->area.y, + e->area.width, + e->area.height); +#endif + + if ( e->area.x <= cliprect->x && + e->area.y <= cliprect->y && + e->area.width >= cliprect->width && + e->area.height >= cliprect->height) { + + /* area completely covers cliprect, cliprect becomes the area */ + res->x = cliprect->x; + res->y = cliprect->y; + res->width = cliprect->width; + res->height = cliprect->height; + + } else if ( e->area.x + e->area.width < cliprect->x || + e->area.x + e->area.width > cliprect->x + cliprect->width || + e->area.y + e->area.height < cliprect->y || + e->area.y + e->area.height > cliprect->y + cliprect->height) { + + /* area has at least one axis with zero overlap, so they can't overlap */ + return 0; + + } else { + + /* areas partially overlap */ + res->x = MAX(e->area.x, cliprect->x); + res->width = MIN(e->area.x + e->area.width, cliprect->x + cliprect->width) - res->x; + + res->y = MAX(e->area.y, cliprect->y); + res->height = MIN(e->area.y + e->area.height, cliprect->y + cliprect->height) - res->y; + + if (!res->width || !res->height) + return 0; + } + +#if 0 + printf("clipped to %x,%x %x,%x\n", + res->x, + res->y, + res->width, + res->height); +#endif + + return 1; +} + + +/* Try align xrect to even boundaries relative to cliprect, + * this is done for the UV routines which operate at 2x2 rgb pixel granularity. + */ +static void uv_align(XRectangle *cliprect, XRectangle *xrect) { + XRectangle rel; + + rel.x = xrect->x - cliprect->x; + rel.y = xrect->y - cliprect->y; + + if (rel.x % 2) { + rel.x -= 1; + rel.width = xrect->width + 1; + } else { + rel.width = xrect->width; + } + + if (rel.y % 2) { + rel.y -= 1; + rel.height = xrect->height + 1; + } else { + rel.height = xrect->height; + } + + /* XXX: note when cliprect prevents an even width, we can't do anything + * about it. rmd could force even-sized recording windows to prevent + * this, but that would be kind of annoying. For now it's assumed + * the UV routines will handle the exception - which might temporarily + * mean they just lop the odd row/column off the edge. + */ + if (rel.width % 2 && rel.width + rel.x < cliprect->x + cliprect->width) + rel.width++; + + if (rel.height % 2 && rel.height + rel.y < cliprect->y + cliprect->height) + rel.height++; +} + + +void rmdInitEventsPolling(ProgData *pdata) { + Window root_return, + parent_return, + *children; + unsigned int i, + nchildren; + + XSelectInput (pdata->dpy, pdata->specs.root, SubstructureNotifyMask); + + if (!pdata->args.full_shots) { + XQueryTree ( pdata->dpy, + pdata->specs.root, + &root_return, + &parent_return, + &children, + &nchildren); + + for (i = 0; i < nchildren; i++) { + XWindowAttributes attribs; + if (XGetWindowAttributes (pdata->dpy, children[i], &attribs)) { + if (!attribs.override_redirect && attribs.depth == pdata->specs.depth) + XDamageCreate( pdata->dpy, + children[i], + XDamageReportRawRectangles); + } + } + XFree(children); + XDamageCreate( pdata->dpy, + pdata->specs.root, + XDamageReportRawRectangles); + } +} + + +void rmdEventLoop(ProgData *pdata) { + int inserts = 0; + + XEvent event; + + while (XPending(pdata->dpy)) { + XNextEvent(pdata->dpy, &event); + if (event.type == KeyPress) { + XKeyEvent *e = (XKeyEvent *)(&event); + if (e->keycode == pdata->pause_key.key) { + int i = 0; + int found = 0; + for (i = 0; i < pdata->pause_key.modnum; i++) { + if (pdata->pause_key.mask[i] == e->state) { + found = 1; + break; + } + } + if (found) { + raise(SIGUSR1); + continue; + } + } + if (e->keycode == pdata->stop_key.key) { + int i = 0; + int found = 0; + for (i = 0; i < pdata->stop_key.modnum; i++) { + if (pdata->stop_key.mask[i] == e->state) { + found = 1; + break; + } + } + if (found) { + raise(SIGINT); + continue; + } + } + } else if (event.type == Expose) { + + if (event.xexpose.count != 0) + continue; + else if (!pdata->args.noframe) { + rmdDrawFrame( pdata->dpy, + pdata->specs.screen, + pdata->shaped_w, + pdata->brwin.rrect.width, + pdata->brwin.rrect.height); + } + + } else if (!pdata->args.full_shots) { + if (event.type == MapNotify ) { + XWindowAttributes attribs; + + if (!((XMapEvent *)(&event))->override_redirect&& + XGetWindowAttributes( pdata->dpy, + event.xcreatewindow.window, + &attribs)) { + + if (!attribs.override_redirect && attribs.depth == pdata->specs.depth) + XDamageCreate( pdata->dpy, + event.xcreatewindow.window, + XDamageReportRawRectangles); + } + } else if (event.type == pdata->damage_event + XDamageNotify ) { + XDamageNotifyEvent *e = (XDamageNotifyEvent *)&event; + XRectangle xrect; + + if (clip_event_area(e, &pdata->brwin.rrect, &xrect)) { + uv_align(&pdata->brwin.rrect, &xrect); + inserts += rmdRectInsert(&pdata->rect_root, &xrect); + } + } + } + } +} diff --git a/src/rmd_poll_events.h b/src/rmd_poll_events.h new file mode 100644 index 0000000..a1902cb --- /dev/null +++ b/src/rmd_poll_events.h @@ -0,0 +1,49 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef POLL_EVENTS_H +#define POLL_EVENTS_H 1 + +#include "rmd_types.h" + + +/** +* Start listening to damage and substructure notify events +* (needed before rmdEventLoop call) +* \param pdata ProgData struct containing all program data +*/ +void rmdInitEventsPolling(ProgData *pdata); + +/** +* Loop calling XNextEvent.Retrieve and place on +* list damage events that arive, create damage for new windows +* and pickup key events for shortcuts. +* \param pdata ProgData struct containing all program data +*/ +void rmdEventLoop(ProgData *pdata); + + +#endif diff --git a/src/rmd_queryextensions.c b/src/rmd_queryextensions.c new file mode 100644 index 0000000..4484866 --- /dev/null +++ b/src/rmd_queryextensions.c @@ -0,0 +1,88 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_queryextensions.h" + +#include "rmd_types.h" + +#include <X11/extensions/shape.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xdamage.h> +#include <stdlib.h> + + + +void rmdQueryExtensions( Display *dpy, + ProgArgs *args, + int *damage_event, + int *damage_error, + int *shm_opcode) { + int xf_event_basep, + xf_error_basep, + shm_event_base, + shm_error_base, + shape_event_base, + shape_error_base; + + if ( !args->full_shots && + !XDamageQueryExtension(dpy, damage_event, damage_error)) { + + fprintf(stderr, "XDamage extension not found!!!\n" + "Try again using the --full-shots option, though\n" + "enabling XDamage is highly recommended,\n" + "for performance reasons.\n"); + exit(4); + } + + if ( !args->noshared && + !XQueryExtension(dpy, "MIT-SHM", shm_opcode, &shm_event_base, &shm_error_base)) { + + args->noshared = 1; + fprintf(stderr, "Shared Memory extension not present!\n" + "Try again using the --no-shared option\n"); + exit(5); + } + + if ( args->xfixes_cursor && + XFixesQueryExtension(dpy, &xf_event_basep, &xf_error_basep) == False) { + + args->xfixes_cursor = 0; + fprintf(stderr, "Xfixes extension not present!\n" + "Please run with the -dummy-cursor or" + " --no-cursor option.\n"); + exit(6); + } + + if ( !args->noframe && + !XShapeQueryExtension(dpy, &shape_event_base, &shape_error_base)) { + + fprintf(stderr, "XShape Not Found!!!\n" + "Frame won't be available.\n"); + + args->noframe = 1; + } +} diff --git a/src/rmd_queryextensions.h b/src/rmd_queryextensions.h new file mode 100644 index 0000000..432fa6c --- /dev/null +++ b/src/rmd_queryextensions.h @@ -0,0 +1,53 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef QUERYEXTENSIONS_H +#define QUERYEXTENSIONS_H 1 + +#include "rmd_types.h" + + +/** +* Check if needed extensions are present +* +* \param dpy Connection to the server +* +* \param args ProgArgs struct containing the user-set options +* +* \param damage_event gets filled with damage event number +* +* \param damage_error gets filled with damage error number +* +* \note Can be an exit point if extensions are not found +*/ +void rmdQueryExtensions(Display *dpy, + ProgArgs *args, + int *damage_event, + int *damage_error, + int *shm_opcode); + + +#endif diff --git a/src/rmd_rectinsert.c b/src/rmd_rectinsert.c new file mode 100644 index 0000000..2d2fd8c --- /dev/null +++ b/src/rmd_rectinsert.c @@ -0,0 +1,480 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_rectinsert.h" + +#include "rmd_types.h" + +#include <stdlib.h> + + +/** +* Collide two rectangles and dictate most sane action for insertion, +* as well as provide the updated rectangle(s) +* \param xrect1 resident rectangle +* +* \param xrect2 New rectangle +* +* \param xrect_return Pointer to rectangles to be inserted +* +* \param nrects number of entries in xrect_return +* +* \retval 0 No collision +* +* \retval 1 xrect1 is covered by xrect2 +* +* \retval 2 xrect2 is covered by xrect1 +* +* \retval -1 xrect1 was broken (new is picked up in xrect_return) +* +* \retval -2 xrect2 was broken (new is picked up in xrect_return) +* +* \retval -10 Grouping the two rects is possible +* +*/ +static int rmdCollideRects( const XRectangle *xrect1, + const XRectangle *xrect2, + XRectangle xrect_return[], + int *nrects) { + if ((xrect1->x>=xrect2->x)&& + (xrect1->x+xrect1->width<=xrect2->x+xrect2->width)&& + (xrect1->y>=xrect2->y)&& + (xrect1->y+xrect1->height<=xrect2->y+xrect2->height)) { + //1 fits in 2 + *nrects=0; + return 1; + } else if ((xrect2->x>=xrect1->x)&& + (xrect2->x+xrect2->width<=xrect1->x+xrect1->width)&& + (xrect2->y>=xrect1->y)&& + (xrect2->y+xrect2->height<=xrect1->y+xrect1->height)) { + //2 fits in 1 + *nrects=0; + return 2; + } else if ((xrect1->x+xrect1->width<xrect2->x)|| + (xrect2->x+xrect2->width<xrect1->x)|| + (xrect1->y+xrect1->height<xrect2->y)|| + (xrect2->y+xrect2->height<xrect1->y)) { + //no collision + *nrects=0; + return 0; + } else { +//overlapping points are considered enclosed +//this happens because libxdamage may generate many events for one change +//and some of them may be in the the exact same region +//so identical rects would be considered not colliding +//in order though to avoid endless recursion on the rmdRectInsert +//function should always start at the next element(which is logical since +//if any rect makes it to a points none of it's part collides with previous +//nodes on the list, too) + int x1[2]={xrect1->x,xrect1->x+xrect1->width}; + int y1[2]={xrect1->y,xrect1->y+xrect1->height}; + int x2[2]={xrect2->x,xrect2->x+xrect2->width}; + int y2[2]={xrect2->y,xrect2->y+xrect2->height}; + int enclosed[2][4],tot1,tot2; + enclosed[0][0]=(((x1[0]>=x2[0])&&(x1[0]<=x2[1])&& + (y1[0]>=y2[0])&&(y1[0]<=y2[1]))?1:0); + enclosed[0][1]=(((x1[1]>=x2[0])&&(x1[1]<=x2[1])&& + (y1[0]>=y2[0])&&(y1[0]<=y2[1]))?1:0); + enclosed[0][2]=(((x1[0]>=x2[0])&&(x1[0]<=x2[1])&& + (y1[1]>=y2[0])&&(y1[1]<=y2[1]))?1:0); + enclosed[0][3]=(((x1[1]>=x2[0])&&(x1[1]<=x2[1])&& + (y1[1]>=y2[0])&&(y1[1]<=y2[1]))?1:0); + enclosed[1][0]=(((x2[0]>=x1[0])&&(x2[0]<=x1[1])&& + (y2[0]>=y1[0])&&(y2[0]<=y1[1]))?1:0); + enclosed[1][1]=(((x2[1]>=x1[0])&&(x2[1]<=x1[1])&& + (y2[0]>=y1[0])&&(y2[0]<=y1[1]))?1:0); + enclosed[1][2]=(((x2[0]>=x1[0])&&(x2[0]<=x1[1])&& + (y2[1]>=y1[0])&&(y2[1]<=y1[1]))?1:0); + enclosed[1][3]=(((x2[1]>=x1[0])&&(x2[1]<=x1[1])&& + (y2[1]>=y1[0])&&(y2[1]<=y1[1]))?1:0); + tot1=enclosed[0][0]+enclosed[0][1]+enclosed[0][2]+enclosed[0][3]; + tot2=enclosed[1][0]+enclosed[1][1]+enclosed[1][2]+enclosed[1][3]; + if ((tot1==2)&&(tot2==2)) {//same width or height, which is the best case + //group + if ((enclosed[1][0]&&enclosed[1][1])&& + (xrect1->width==xrect2->width)) { + *nrects=1; + xrect_return[0].x = xrect1->x; + xrect_return[0].y = xrect1->y; + xrect_return[0].width = xrect1->width; + xrect_return[0].height = xrect2->height + xrect2->y - xrect1->y; + return -10; + } else if ((enclosed[1][0]&&enclosed[1][2])&& + (xrect1->height==xrect2->height)) { + *nrects=1; + xrect_return[0].x = xrect1->x; + xrect_return[0].y = xrect1->y; + xrect_return[0].width = xrect2->width + xrect2->x - xrect1->x; + xrect_return[0].height = xrect1->height; + return -10; + } else if ((enclosed[1][3]&&enclosed[1][1])&& + (xrect1->height==xrect2->height)) { + *nrects=1; + xrect_return[0].x = xrect2->x; + xrect_return[0].y = xrect2->y; + xrect_return[0].width = xrect1->width + xrect1->x - xrect2->x; + xrect_return[0].height = xrect2->height; + return -10; + } else if ((enclosed[1][3]&&enclosed[1][2])&& + (xrect1->width==xrect2->width)) { + *nrects=1; + xrect_return[0].x = xrect2->x; + xrect_return[0].y = xrect2->y; + xrect_return[0].width = xrect2->width; + xrect_return[0].height = xrect1->height + xrect1->y - xrect2->y; + return -10; + } + //if control reaches here therewasn't a group and we go on + } + + if (tot2==2) { + //break rect2 + xrect_return[0].x = xrect2->x; + xrect_return[0].y = xrect2->y; + xrect_return[0].width = xrect2->width; + xrect_return[0].height = xrect2->height; + *nrects=1; + if (enclosed[1][0]&&enclosed[1][1]) { + xrect_return[0].y = y1[1]; + xrect_return[0].height -= y1[1] - y2[0]; + } else if (enclosed[1][0]&&enclosed[1][2]) { + xrect_return[0].x = x1[1]; + xrect_return[0].width -= x1[1] - x2[0]; + } else if (enclosed[1][3]&&enclosed[1][1]) + xrect_return[0].width -= x2[1] - x1[0]; + else if (enclosed[1][3]&&enclosed[1][2]) + xrect_return[0].height -= y2[1] - y1[0]; + return -2; + } else if (tot1==2) { + //if the first one breaks(which is already inserted) + //then we reenter the part that was left and the one + //that was to be inserted + xrect_return[1].x = xrect2->x; + xrect_return[1].y = xrect2->y; + xrect_return[1].width = xrect2->width; + xrect_return[1].height = xrect2->height; + xrect_return[0].x = xrect1->x; + xrect_return[0].y = xrect1->y; + xrect_return[0].width = xrect1->width; + xrect_return[0].height = xrect1->height; + *nrects=1; + if (enclosed[0][0]&&enclosed[0][1]) { + xrect_return[0].y = y2[1]; + xrect_return[0].height -= y2[1] - y1[0]; + } else if (enclosed[0][0]&&enclosed[0][2]) { + xrect_return[0].x = x2[1]; + xrect_return[0].width -= x2[1] - x1[0]; + } else if (enclosed[0][3]&&enclosed[0][1]) + xrect_return[0].width -= x1[1] - x2[0]; + else if (enclosed[0][3]&&enclosed[0][2]) + xrect_return[0].height -= y1[1] - y2[0]; + return -1; + + } else if (tot2==1) { //in which case there is also tot1==1 + //but we rather not break that + //break rect2 in two + *nrects=2; + if (enclosed[1][0]) { +//first + xrect_return[0].x = x1[1]; + xrect_return[0].y = y2[0]; + xrect_return[0].width = xrect2->width - x1[1] + x2[0]; + xrect_return[0].height = xrect2->height; +//second + xrect_return[1].x = x2[0]; + xrect_return[1].y = y1[1]; + xrect_return[1].width = x1[1] - x2[0]; + xrect_return[1].height = xrect2->height - y1[1] + y2[0]; + } else if (enclosed[1][1]) { +//first + xrect_return[0].x = x2[0]; + xrect_return[0].y = y2[0]; + xrect_return[0].width = xrect2->width - x2[1] + x1[0]; + xrect_return[0].height = xrect2->height; +//second + xrect_return[1].x = x1[0]; + xrect_return[1].y = y1[1]; + xrect_return[1].width = x2[1] - x1[0]; + xrect_return[1].height = xrect2->height - y1[1] + y2[0]; + } else if (enclosed[1][2]) { +//first(same as [1][0]) + xrect_return[0].x = x1[1]; + xrect_return[0].y = y2[0]; + xrect_return[0].width = xrect2->width - x1[1] + x2[0]; + xrect_return[0].height = xrect2->height; +//second + xrect_return[1].x = x2[0]; + xrect_return[1].y = y2[0]; + xrect_return[1].width = x1[1] - x2[0]; + xrect_return[1].height = xrect2->height - y2[1] + y1[0]; + } else if (enclosed[1][3]) { +//first(same as [1][1]) + xrect_return[0].x = x2[0]; + xrect_return[0].y = y2[0]; + xrect_return[0].width = xrect2->width - x2[1] + x1[0]; + xrect_return[0].height = xrect2->height; +//second + xrect_return[1].x = x1[0]; + xrect_return[1].y = y2[0]; + xrect_return[1].width = x2[1] - x1[0]; + xrect_return[1].height = xrect2->height - y2[1] + y1[0]; + } + return -2; + } else {//polygons collide but no point of one is in the other + //so we just keep the two parts of rect2 that are outside rect1 + + //break rect2 in two + //two cases: + //rect2 crossing vertically rect1 + //and rect2 crossing horizontally rect1 + //The proper one can be found by simply checking x,y positions + *nrects=2; + if (xrect2->y<xrect1->y) { + //common + xrect_return[0].x = xrect_return[1].x = x2[0]; + xrect_return[0].width = xrect_return[1].width = xrect2->width; + + xrect_return[0].y = y2[0]; + xrect_return[0].height = xrect2->height - y2[1] + y1[0]; + xrect_return[1].y = y1[1]; + xrect_return[1].height = y2[1] - y1[1]; + } else { + //common + xrect_return[0].y = xrect_return[1].y = y2[0]; + xrect_return[0].height = xrect_return[1].height = xrect2->height; + + xrect_return[0].x = x2[0]; + xrect_return[0].width = xrect2->width - x2[1] + x1[0]; + xrect_return[1].x = x1[1]; + xrect_return[1].width = x2[1] - x1[1]; + } + return -2; + } + } +} + +int rmdRectInsert(RectArea **root, const XRectangle *xrect) { + + int total_insertions = 0; + RectArea *temp = NULL, *newnode = (RectArea *)malloc(sizeof(RectArea)); + + newnode->rect.x = xrect->x; + newnode->rect.y = xrect->y; + newnode->rect.width = xrect->width; + newnode->rect.height = xrect->height; + newnode->prev = newnode->next = NULL; + + if (*root == NULL) { + *root = newnode; + total_insertions = 1; + } else { + XRectangle xrect_return[2]; + int nrects = 0, insert_ok = 1, i = 0; + + temp = *root; + + while (insert_ok) { //if something is broken list does not procceed + //(except on -1 collres case) + + int collres = rmdCollideRects(&temp->rect, xrect, &xrect_return[0], &nrects); + if ((!collres)) + insert_ok = 1; + else { + insert_ok=0; + switch (collres) { + case 1://remove current node,reinsert new one + total_insertions--; + if (temp->prev!=NULL) {//no root + if (temp->next!=NULL) {// + RectArea *temp1=temp->next; + temp->prev->next=temp->next; + temp->next->prev=temp->prev; + free(temp); + if ((xrect->width>0)&&(xrect->height>0)) + total_insertions+=rmdRectInsert(&temp1,xrect); + } else { + temp->prev->next=newnode; + newnode->prev=temp->prev; + total_insertions++; + free(temp); + } + } else {//root + if ((*root)->next!=NULL) { + (*root)=(*root)->next; + (*root)->prev=NULL; + if ((xrect->width>0)&&(xrect->height>0)) + total_insertions+=rmdRectInsert(root,xrect); + } else if ((xrect->width>0)&&(xrect->height>0)) { + *root=newnode; + total_insertions++; + } else { + *root=NULL; + total_insertions++; + } + free(temp); + } + break; + case 2://done,area is already covered + free(newnode); + break; + case -1://current node is broken and reinserted + //(in same pos) + //newnode is also reinserted + if (xrect_return[0].width > 0 && + xrect_return[0].height > 0) { + temp->rect.x = xrect_return[0].x; + temp->rect.y = xrect_return[0].y; + temp->rect.width = xrect_return[0].width; + temp->rect.height = xrect_return[0].height; + if (temp->next==NULL) { + temp->next=newnode; + newnode->prev=temp; + total_insertions++; + } else { + insert_ok=1; + } + } else {//it might happen that the old and now broken node + //is of zero width or height + //(so it isn't reinserted) + if ((temp->prev==NULL)&&(temp->next!=NULL)) { + *root=(*root)->next; + (*root)->prev=NULL; + } else if ((temp->next==NULL)&&(temp->prev!=NULL)) { + temp->prev->next=newnode; + newnode->prev=temp->prev; + } else if ((temp->next==NULL)&&(temp->prev==NULL)) + (*root)=newnode; else { + total_insertions--; + temp->next->prev=temp->prev; + temp->prev->next=temp->next; + total_insertions+=rmdRectInsert(&temp->next, xrect); + } + free(temp); + } + break; + case -2://new is broken and reinserted + if (temp->next==NULL) { + total_insertions+=nrects; + newnode->rect.x = xrect_return[0].x; + newnode->rect.y = xrect_return[0].y; + newnode->rect.width = xrect_return[0].width; + newnode->rect.height = xrect_return[0].height; + temp->next=newnode; + newnode->prev=temp; + if (nrects>1) { + RectArea *newnode1= + (RectArea *)malloc(sizeof(RectArea)); + + newnode1->rect.x = xrect_return[1].x; + newnode1->rect.y = xrect_return[1].y; + newnode1->rect.width = xrect_return[1].width; + newnode1->rect.height = xrect_return[1].height; + newnode->next=newnode1; + newnode1->prev=newnode; + newnode1->next=NULL; + } + } else { + for(i=0;i<nrects;i++) { + if (xrect_return[i].width > 0 && + xrect_return[i].height > 0) + total_insertions+= + rmdRectInsert(&temp->next, &xrect_return[i]); + } + } + break; + case -10://grouped + if (temp->prev==NULL) { + if (temp->next==NULL) {//empty list + newnode->rect.x = xrect_return[0].x; + newnode->rect.y = xrect_return[0].y; + newnode->rect.width = xrect_return[0].width; + newnode->rect.height = xrect_return[0].height; + + *root=newnode; + free(temp); + } else { + total_insertions--; + *root=temp->next; + (*root)->prev=NULL; + free(temp); + if (xrect_return[0].width > 0 && + xrect_return[0].height > 0) + total_insertions+= + rmdRectInsert(root, &xrect_return[0]); + } + } else if (temp->next==NULL) {//last, enter anyway + newnode->rect.x = xrect_return[0].x; + newnode->rect.y = xrect_return[0].y; + newnode->rect.width = xrect_return[0].width; + newnode->rect.height = xrect_return[0].height; + temp->prev->next=newnode; + newnode->prev=temp->prev; + free(temp); + } else {//remove node and reinsert, starting where we were + total_insertions--; + RectArea *temp1=temp->next; + temp->prev->next=temp->next; + temp->next->prev=temp->prev; + free(temp); + if (xrect_return[0].width > 0 && + xrect_return[0].height > 0) + total_insertions+= + rmdRectInsert(&temp1, &xrect_return[0]); + } + break; + } + } + if (insert_ok) { + if (temp->next==NULL) { + temp->next=newnode; + newnode->prev=temp; + total_insertions++; + break; + } else { + temp=temp->next; + } + } else { + break; + } + }; + } + return total_insertions; +} + +void rmdClearList(RectArea **root) { + + RectArea *temp; + temp=*root; + if (temp!=NULL) { + while (temp->next!=NULL) { + temp=temp->next; + free(temp->prev); + } + free(temp); + *root=NULL; + } +} diff --git a/src/rmd_rectinsert.h b/src/rmd_rectinsert.h new file mode 100644 index 0000000..d9d0a9c --- /dev/null +++ b/src/rmd_rectinsert.h @@ -0,0 +1,55 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RECTINSERT_H +#define RECTINSERT_H 1 + + +#include "rmd_types.h" + + +/** +* Insert a new rectangle on the list, making sure it doesn't overlap +* with the existing ones +* \param root Root entry of the list +* +* \param xrect New area to be inserted +* +* \returns Number of insertions during operation +* +* \note This function is reentrant and recursive. The number +* of insertions takes this into account. +*/ +int rmdRectInsert(RectArea **root, const XRectangle *xrect); + +/** +* Clean up a list of areas marked for update. +* \param root Root entry of the list +*/ +void rmdClearList(RectArea **root); + + +#endif diff --git a/src/rmd_register_callbacks.c b/src/rmd_register_callbacks.c new file mode 100644 index 0000000..33e04dd --- /dev/null +++ b/src/rmd_register_callbacks.c @@ -0,0 +1,86 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_register_callbacks.h" + +#include "rmd_types.h" + +#include <signal.h> + + +// There seem to be no way of passing user data to the signal handler, +// so hack around not being able to pass ProgData to them +static int *pdata_running = NULL; +static int *pdata_paused = NULL; +static int *pdata_aborted = NULL; +static int *pdata_pause_state_changed = NULL; + + +static void rmdSetPaused(int signum) { + + *pdata_pause_state_changed = TRUE; +} + +static void rmdSetRunning(int signum) { + + if (!*pdata_paused) { + + *pdata_running = FALSE; + + if (signum == SIGABRT) { + *pdata_aborted = TRUE; + } + } +} + +void rmdRegisterCallbacks(ProgData *pdata) { + + struct sigaction pause_act; + struct sigaction end_act; + + // Is there some way to pass pdata to the signal handlers? + pdata_running = &pdata->running; + pdata_paused = &pdata->paused; + pdata_aborted = &pdata->aborted; + pdata_pause_state_changed = &pdata->pause_state_changed; + + // Setup pause_act + sigfillset(&pause_act.sa_mask); + pause_act.sa_flags = SA_RESTART; + pause_act.sa_handler = rmdSetPaused; + + sigaction(SIGUSR1, &pause_act, NULL); + + // Setup end_act + sigfillset(&end_act.sa_mask); + end_act.sa_flags = SA_RESTART; + end_act.sa_handler = rmdSetRunning; + + sigaction(SIGINT, &end_act, NULL); + sigaction(SIGTERM, &end_act, NULL); + sigaction(SIGABRT, &end_act, NULL); +} diff --git a/src/rmd_register_callbacks.h b/src/rmd_register_callbacks.h new file mode 100644 index 0000000..abe8783 --- /dev/null +++ b/src/rmd_register_callbacks.h @@ -0,0 +1,41 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef REGISTER_CALLBACKS_H +#define REGISTER_CALLBACKS_H 1 + + +#include "rmd_types.h" + + +/** +* Set up all callbacks and signal handlers +* \param pdata ProgData struct containing all program data +*/ +void rmdRegisterCallbacks(ProgData *prog_data); + + +#endif diff --git a/src/rmd_rescue.c b/src/rmd_rescue.c new file mode 100644 index 0000000..84bcf4b --- /dev/null +++ b/src/rmd_rescue.c @@ -0,0 +1,124 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_rescue.h" + +#include "rmd_yuv_utils.h" +#include "rmd_encode_cache.h" +#include "rmd_initialize_data.h" +#include "rmd_register_callbacks.h" +#include "rmd_specsfile.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <string.h> +#include <stdlib.h> + + +int rmdRescue(const char *path) { + unsigned short width, height; + ProgData pdata; + EncData enc_data; + CacheData cache_data; + + rmdSetupDefaultArgs(&pdata.args); + + pdata.enc_data = &enc_data; + pdata.cache_data = &cache_data; + + //projname + cache_data.projname = malloc(strlen(path) + 2); + strcpy(cache_data.projname, path); + strcat(cache_data.projname, "/");//having two of these doesn't hurt... + //image data + cache_data.imgdata = malloc(strlen(cache_data.projname) + 11); + strcpy(cache_data.imgdata, cache_data.projname); + strcat(cache_data.imgdata, "img.out"); + //audio data + cache_data.audiodata = malloc(strlen(cache_data.projname) + 10); + strcpy(cache_data.audiodata, cache_data.projname); + strcat(cache_data.audiodata, "audio.pcm"); + //specsfile + cache_data.specsfile = malloc(strlen(cache_data.projname) + 10); + strcpy(cache_data.specsfile, cache_data.projname); + strcat(cache_data.specsfile, "specs.txt"); + + if (rmdReadSpecsFile(&pdata)) + return 1; + + width = ((pdata.brwin.rrect.width + 15) >> 4) << 4; + height = ((pdata.brwin.rrect.height + 15) >> 4) << 4; + + enc_data.yuv.y = malloc(height * width); + enc_data.yuv.u = malloc(height * width / 4); + enc_data.yuv.v = malloc(height * width / 4); + enc_data.yuv.y_width = width; + enc_data.yuv.y_height = height; + enc_data.yuv.y_stride = width; + + enc_data.yuv.uv_width = width / 2; + enc_data.yuv.uv_height = height / 2; + enc_data.yuv.uv_stride = width / 2; + + for (int i = 0; i < (enc_data.yuv.y_width * enc_data.yuv.y_height); i++) + enc_data.yuv.y[i] = 0; + + for (int i = 0; i < (enc_data.yuv.uv_width * enc_data.yuv.uv_height); i++) + enc_data.yuv.v[i] = enc_data.yuv.u[i] = 127; + + yblocks = malloc(sizeof(*yblocks) * (enc_data.yuv.y_width / Y_UNIT_WIDTH) * + (enc_data.yuv.y_height / Y_UNIT_WIDTH)); + ublocks = malloc(sizeof(*ublocks) * (enc_data.yuv.y_width / Y_UNIT_WIDTH) * + (enc_data.yuv.y_height / Y_UNIT_WIDTH)); + vblocks = malloc(sizeof(*vblocks) * (enc_data.yuv.y_width / Y_UNIT_WIDTH) * + (enc_data.yuv.y_height / Y_UNIT_WIDTH)); + + pdata.frametime = 1000000 / pdata.args.fps; + + pthread_mutex_init(&pdata.theora_lib_mutex, NULL); + pthread_mutex_init(&pdata.vorbis_lib_mutex, NULL); + pthread_mutex_init(&pdata.libogg_mutex, NULL); + pthread_cond_init(&pdata.theora_lib_clean, NULL); + pthread_cond_init(&pdata.vorbis_lib_clean, NULL); + pdata.th_encoding_clean = pdata.v_encoding_clean = 1; + pdata.avd = 0; + pdata.sound_buffer = NULL; + pdata.running = TRUE; + pdata.aborted = FALSE; + + rmdRegisterCallbacks(&pdata); + fprintf(stderr, "Restoring %s!!!\n", path); + + rmdEncodeCache(&pdata); + + fprintf(stderr, "Done!!!\n"); + fprintf(stderr, "Goodbye!\n"); + rmdCleanUp(); + + return 0; +} diff --git a/src/rmd_rescue.h b/src/rmd_rescue.h new file mode 100644 index 0000000..2fbf2da --- /dev/null +++ b/src/rmd_rescue.h @@ -0,0 +1,42 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_RESCUE_H +#define RMD_RESCUE_H 1 + +/* + * Rescue a previous recording, found in + * the given path. + * + * \param path Path to the cache folder. + * + * \returns 0 on Success, 1 on failure + * + */ +int rmdRescue(const char *path); + + +#endif diff --git a/src/rmd_setbrwindow.c b/src/rmd_setbrwindow.c new file mode 100644 index 0000000..9ad30b8 --- /dev/null +++ b/src/rmd_setbrwindow.c @@ -0,0 +1,115 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_setbrwindow.h" + +#include "rmd_types.h" + + +boolean rmdSetBRWindow( Display *dpy, + BRWindow *brwin, + DisplaySpecs *specs, + ProgArgs *args) { + + //before we start recording we have to make sure the ranges are valid + if (args->windowid == 0) {//root window + //first set it up + brwin->windowid = specs->root; + brwin->winrect.x = 0; + brwin->winrect.y = 0; + brwin->winrect.width = specs->width; + brwin->winrect.height = specs->height; + + brwin->rrect.x = args->x; + brwin->rrect.y = args->y; + brwin->rrect.width = args->width ? args->width : brwin->winrect.width - args->x; + brwin->rrect.height = args->height ? args->height : brwin->winrect.height - args->y; + + //and then check validity + if ( brwin->rrect.x + brwin->rrect.width > specs->width || + brwin->rrect.y + brwin->rrect.height > specs->height) { + + fprintf(stderr, "Window size specification out of bounds!" + "(current resolution:%dx%d)\n", + specs->width, specs->height); + return FALSE; + } + } else { + int transl_x, transl_y; + Window wchid; + XWindowAttributes attribs; + + XGetWindowAttributes(dpy, args->windowid, &attribs); + + if (attribs.map_state == IsUnviewable || attribs.map_state == IsUnmapped) { + fprintf(stderr, "Window must be mapped and visible!\n"); + return FALSE; + } + + XTranslateCoordinates( dpy, + specs->root, + args->windowid, + attribs.x, + attribs.y, + &transl_x, + &transl_y, + &wchid); + + brwin->winrect.x = attribs.x - transl_x; + brwin->winrect.y = attribs.y - transl_y; + brwin->winrect.width = attribs.width; + brwin->winrect.height = attribs.height; + + /* XXX FIXME: this check is partial at best, surely windows can be off the low + * sides of the screen too... + */ + if ( brwin->winrect.x + brwin->winrect.width > specs->width || + brwin->winrect.y + brwin->winrect.height > specs->height) { + + fprintf(stderr,"Window must be on visible screen area!\n"); + return FALSE; + } + + brwin->rrect.x = brwin->winrect.x + args->x; + brwin->rrect.y = brwin->winrect.y + args->y; + brwin->rrect.width = args->width ? args->width : brwin->winrect.width - args->x; + brwin->rrect.height = args->height ? args->height : brwin->winrect.height - args->y; + + if ( args->x + brwin->rrect.width > brwin->winrect.width || + args->y + brwin->rrect.height > brwin->winrect.height) { + fprintf(stderr, "Specified Area is larger than window!\n"); + return FALSE; + } + } + + fprintf(stderr, "Initial recording window is set to:\n" + "X:%d Y:%d Width:%d Height:%d\n", + brwin->rrect.x, brwin->rrect.y, + brwin->rrect.width, brwin->rrect.height); + + return TRUE; +} diff --git a/src/rmd_setbrwindow.h b/src/rmd_setbrwindow.h new file mode 100644 index 0000000..e036856 --- /dev/null +++ b/src/rmd_setbrwindow.h @@ -0,0 +1,53 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef SETBRWINDOW_H +#define SETBRWINDOW_H 1 + +#include "rmd_types.h" + + +/** +* Check and align window size +* +* \param dpy Connection to the server +* +* \param brwin BRWindow struct contaning the initial and final window +* +* \param specs DisplaySpecs struct with +* information about the display to be recorded +* +* \param args ProgArgs struct containing the user-set options +* +* \returns 0 on Success 1 on Failure +*/ +int rmdSetBRWindow(Display *dpy, + BRWindow *brwin, + DisplaySpecs *specs, + ProgArgs *args); + + +#endif diff --git a/src/rmd_shortcuts.c b/src/rmd_shortcuts.c new file mode 100644 index 0000000..e1e07f5 --- /dev/null +++ b/src/rmd_shortcuts.c @@ -0,0 +1,142 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_shortcuts.h" + +#include "rmd_types.h" + +#include <X11/Xlib.h> +#include <X11/Xlibint.h> +#include <X11/keysym.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int rmdRegisterShortcut( Display *dpy, + Window root, + const char *shortcut, + HotKey *hotkey) { + + int keycode = 0, i, j; + KeySym key = 0; + unsigned int modifier_mask = 0, numlock_mask = 0; + char *keystr = NULL; + + if (strstr(shortcut, "Shift")) + modifier_mask = modifier_mask | ShiftMask; + if (strstr(shortcut, "Control")) + modifier_mask = modifier_mask | ControlMask; + if (strstr(shortcut, "Mod1")) + modifier_mask = modifier_mask | Mod1Mask; + if (strstr(shortcut, "Mod2")) + modifier_mask = modifier_mask | Mod2Mask; + if (strstr(shortcut, "Mod3")) + modifier_mask = modifier_mask | Mod3Mask; + if (strstr(shortcut, "Mod4")) + modifier_mask = modifier_mask | Mod4Mask; + if (strstr(shortcut, "Mod5")) + modifier_mask = modifier_mask | Mod5Mask; + + //we register the shortcut on the root + //window, which means on every keypress event, + //so I think it's neccessary to have at least one + //modifier. + if (modifier_mask == 0) + return 1; + if ((keystr = rindex(shortcut, '+')) != NULL) { + keystr++; + if ((key = XStringToKeysym(keystr)) == NoSymbol) + return 1; + else + keycode = XKeysymToKeycode(dpy, key); + } else + return 1; + + + /* Key grabbing stuff taken from tilda who took it from yeahconsole + * who took it from evilwm */ + + { + KeyCode numlock = XKeysymToKeycode(dpy, XK_Num_Lock); + XModifierKeymap *modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) { + for (j = 0; j < modmap->max_keypermod; j++) { + if (modmap->modifiermap[i * modmap->max_keypermod + j] == numlock) + numlock_mask = (1 << i); + } + } + XFreeModifiermap(modmap); + } + + hotkey->modnum = 0; + hotkey->key = keycode; + + XGrabKey( dpy, + keycode, + modifier_mask, + root, + True, + GrabModeAsync, + GrabModeAsync); + hotkey->mask[0] = modifier_mask; + hotkey->modnum++; + + XGrabKey( dpy, + keycode, + LockMask | modifier_mask, + root, + True, + GrabModeAsync, + GrabModeAsync); + hotkey->mask[1] = LockMask | modifier_mask; + hotkey->modnum++; + + if (numlock_mask) { + XGrabKey( dpy, + keycode, + numlock_mask | modifier_mask, + root, + True, + GrabModeAsync, + GrabModeAsync); + hotkey->mask[2] = numlock_mask | modifier_mask; + hotkey->modnum++; + + XGrabKey( dpy, + keycode, + numlock_mask | LockMask | modifier_mask, + root, + True, + GrabModeAsync, + GrabModeAsync); + hotkey->mask[3] = numlock_mask | LockMask | modifier_mask; + hotkey->modnum++; + } + + return 0; +} diff --git a/src/rmd_shortcuts.h b/src/rmd_shortcuts.h new file mode 100644 index 0000000..a0dc2af --- /dev/null +++ b/src/rmd_shortcuts.h @@ -0,0 +1,57 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef SHORTCUTS_H +#define CHORTCUTS_H 1 + +#include "rmd_types.h" + + +/* + * Check a shortcut combination and if valid, + * register it, on the root window. + * + * \param dpy Connection to the X Server + * + * \param root Root window id + * + * \param shortcut String represantation of the shortcut + * + * \param HotKey Pre-allocated struct that is filled with the + * modifiers and the keycode, for checks on incoming + * keypress events. Left untouched if the call fails. + * + * + * \returns 0 on Success, 1 on Failure. + * + */ +int rmdRegisterShortcut(Display *dpy, + Window root, + const char *shortcut, + HotKey *hotkey); + + +#endif diff --git a/src/rmd_specsfile.c b/src/rmd_specsfile.c new file mode 100644 index 0000000..50542a8 --- /dev/null +++ b/src/rmd_specsfile.c @@ -0,0 +1,151 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_specsfile.h" + +#include "rmd_types.h" + +#include <stdlib.h> +#include <stdio.h> + + +int rmdWriteSpecsFile(ProgData *pdata) { + + FILE *fp; + + fp=fopen(pdata->cache_data->specsfile,"wb"); + if (!fp) + return 1; + + fprintf(fp,"recordMyDesktop = %s\n",VERSION); + fprintf(fp,"Width = %hu\n",pdata->brwin.rrect.width); + fprintf(fp,"Height = %hu\n",pdata->brwin.rrect.height); + fprintf(fp,"Filename = %s\n",pdata->args.filename); + fprintf(fp,"FPS = %f\n",pdata->args.fps); + fprintf(fp,"NoSound = %d\n",pdata->args.nosound); + fprintf(fp,"Frequency = %d\n",pdata->args.frequency); + fprintf(fp,"Channels = %d\n",pdata->args.channels); + fprintf(fp,"BufferSize = %lu\n",(unsigned long)pdata->args.buffsize); + fprintf(fp,"SoundFrameSize = %d\n",pdata->sound_framesize); + fprintf(fp,"PeriodSize = %lu\n",(unsigned long)pdata->periodsize); + fprintf(fp,"UsedJack = %d\n",pdata->args.use_jack); + fprintf(fp,"v_bitrate = %d\n",pdata->args.v_bitrate); + fprintf(fp,"v_quality = %d\n",pdata->args.v_quality); + fprintf(fp,"s_quality = %d\n",pdata->args.s_quality); + fprintf(fp,"ZeroCompression = %d\n",pdata->args.zerocompression); + + fclose(fp); + + return 0; +} + +int rmdReadSpecsFile(ProgData *pdata) { + char Cached_Version[256]; + FILE *fp; + + fp=fopen(pdata->cache_data->specsfile,"rb"); + if (!fp) + return 1; + + free(pdata->args.filename); + pdata->args.filename=malloc(512); + + //Take that single-point-of-exit advocates !!! + //15 points of exit, just for your pleasure. + //Also, Vi(m) rules, emacs sucks. + + if (fscanf(fp,"recordMyDesktop = %s\n",Cached_Version)!=1) { + fprintf(stderr,"Error reading VERSION attribute!!!\n"); + return 1; + } + if (fscanf(fp,"Width = %hu\n",&pdata->brwin.rrect.width)!=1) { + fprintf(stderr,"Error reading Width attribute!!!\n"); + return 1; + } + if (fscanf(fp,"Height = %hu\n",&pdata->brwin.rrect.height)!=1) { + fprintf(stderr,"Error reading Height attribute!!!\n"); + return 1; + } + if (fscanf(fp,"Filename = %s\n",pdata->args.filename)!=1) { + fprintf(stderr,"Error reading Filename attribute!!!\n"); + return 1; + } + if (fscanf(fp,"FPS = %f\n",&pdata->args.fps)!=1) { + fprintf(stderr,"Error reading FPS attribute!!!\n"); + return 1; + } + if (fscanf(fp,"NoSound = %d\n",&pdata->args.nosound)!=1) { + fprintf(stderr,"Error reading NoSound attribute!!!\n"); + return 1; + } + if (fscanf(fp,"Frequency = %d\n",&pdata->args.frequency)!=1) { + fprintf(stderr,"Error reading Frequency attribute!!!\n"); + return 1; + } + if (fscanf(fp,"Channels = %d\n",&pdata->args.channels)!=1) { + fprintf(stderr,"Error reading Channels attribute!!!\n"); + return 1; + } + if (fscanf(fp,"BufferSize = %lu\n", + (unsigned long *)&pdata->args.buffsize)!=1) { + fprintf(stderr,"Error reading BufferSize attribute!!!\n"); + return 1; + } + if (fscanf(fp,"SoundFrameSize = %d\n",&pdata->sound_framesize)!=1) { + fprintf(stderr,"Error reading SoundFrameSize attribute!!!\n"); + return 1; + } + if (fscanf(fp,"PeriodSize = %lu\n", + (unsigned long *)&pdata->periodsize)!=1) { + fprintf(stderr,"Error reading PeriodSize attribute!!!\n"); + return 1; + } + if (fscanf(fp,"UsedJack = %u\n",&pdata->args.use_jack)!=1) { + fprintf(stderr,"Error reading UsedJack attribute!!!\n"); + return 1; + } + if (fscanf(fp,"v_bitrate = %d\n",&pdata->args.v_bitrate)!=1) { + fprintf(stderr,"Error reading v_bitrate attribute!!!\n"); + return 1; + } + if (fscanf(fp,"v_quality = %d\n",&pdata->args.v_quality)!=1) { + fprintf(stderr,"Error reading v_quality attribute!!!\n"); + return 1; + } + if (fscanf(fp,"s_quality = %d\n",&pdata->args.s_quality)!=1) { + fprintf(stderr,"Error reading s_quality attribute!!!\n"); + return 1; + } + if (fscanf(fp,"ZeroCompression = %d\n",&pdata->args.zerocompression)!=1) { + fprintf(stderr,"Error reading ZeroCompression attribute!!!\n"); + return 1; + } + + fclose(fp); + + return 0; +} diff --git a/src/rmd_specsfile.h b/src/rmd_specsfile.h new file mode 100644 index 0000000..67cbd46 --- /dev/null +++ b/src/rmd_specsfile.h @@ -0,0 +1,59 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef SPECSFILE_H +#define SPECSFILE_H 1 + +#include "rmd_types.h" + + +/* + * Create a text file that holds the required + * capture attributes, in the cache directory. + * + * \param pdata ProgData struct containing all program data + * + * \returns 0 on Success, 1 on failure + * + */ +int rmdWriteSpecsFile(ProgData *pdata); + + + +/* + * Read the text file that holds the required + * capture attributes, in the cache directory. + * + * \param pdata ProgData struct that will be fille + * with all program data + * + * \returns 0 on Success, 1 on failure + * + */ +int rmdReadSpecsFile(ProgData *pdata); + + +#endif diff --git a/src/rmd_threads.c b/src/rmd_threads.c new file mode 100644 index 0000000..cb81f84 --- /dev/null +++ b/src/rmd_threads.c @@ -0,0 +1,169 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_threads.h" + +#include "rmd_cache_audio.h" +#include "rmd_cache_frame.h" +#include "rmd_capture_sound.h" +#include "rmd_encode_image_buffer.h" +#include "rmd_encode_sound_buffer.h" +#include "rmd_flush_to_ogg.h" +#include "rmd_get_frame.h" +#include "rmd_jack.h" +#include "rmd_register_callbacks.h" +#include "rmd_timer.h" +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +void rmdThreads(ProgData *pdata) { + pthread_t image_capture_t, + image_encode_t, + image_cache_t, + sound_capture_t, + sound_encode_t, + sound_cache_t, + flush_to_ogg_t, + timer_t; + + if (pdata->args.delay > 0) { + fprintf(stderr, "Will sleep for %d seconds now.\n", pdata->args.delay); + sleep(pdata->args.delay); + } + + /*start threads*/ + pthread_create( &image_capture_t, + NULL, + (void *)rmdGetFrame, + (void *)pdata); + + if (pdata->args.encOnTheFly) + pthread_create( &image_encode_t, + NULL, + (void *)rmdEncodeImageBuffer, + (void *)pdata); + else + pthread_create( &image_cache_t, + NULL, + (void *)rmdCacheImageBuffer, + (void *)pdata); + + if (!pdata->args.nosound) { + if (!pdata->args.use_jack) + pthread_create( &sound_capture_t, + NULL, + (void *)rmdCaptureSound, + (void *)pdata); + + if (pdata->args.encOnTheFly) + pthread_create( &sound_encode_t, + NULL, + (void *)rmdEncodeSoundBuffer, + (void *)pdata); + else + pthread_create( &sound_cache_t, + NULL, + (void *)rmdCacheSoundBuffer, + (void *)pdata); + } + + if (pdata->args.encOnTheFly) + pthread_create( &flush_to_ogg_t, + NULL, + (void *)rmdFlushToOgg, + (void *)pdata); + + rmdRegisterCallbacks(pdata); + pdata->timer_alive = 1; + pthread_create( &timer_t, + NULL, + (void *)rmdTimer, + (void *)pdata); + fprintf(stderr,"Capturing!\n"); + +#ifdef HAVE_LIBJACK + if (pdata->args.use_jack) + pdata->jdata->capture_started = 1; +#endif + //wait all threads to finish + + pthread_join(image_capture_t, NULL); + fprintf(stderr,"Shutting down."); + //if no damage events have been received the thread will get stuck + pthread_mutex_lock(&pdata->theora_lib_mutex); + while (!pdata->th_encoding_clean) { + puts("waiting for th_enc"); + pthread_cond_signal(&pdata->image_buffer_ready); + pthread_mutex_unlock(&pdata->theora_lib_mutex); + usleep(10000); + pthread_mutex_lock(&pdata->theora_lib_mutex); + } + pthread_mutex_unlock(&pdata->theora_lib_mutex); + + if (pdata->args.encOnTheFly) + pthread_join(image_encode_t, NULL); + else + pthread_join(image_cache_t, NULL); + fprintf(stderr,"."); + + if (!pdata->args.nosound) { +#ifdef HAVE_LIBJACK + if (pdata->args.use_jack) + rmdStopJackClient(pdata->jdata); +#endif + if (!pdata->args.use_jack) + pthread_join(sound_capture_t,NULL); + + fprintf(stderr,"."); + pthread_mutex_lock(&pdata->vorbis_lib_mutex); + while (!pdata->v_encoding_clean) { + puts("waiting for v_enc"); + pthread_cond_signal(&pdata->sound_data_read); + pthread_mutex_unlock(&pdata->vorbis_lib_mutex); + usleep(10000); + pthread_mutex_lock(&pdata->vorbis_lib_mutex); + } + pthread_mutex_unlock(&pdata->vorbis_lib_mutex); + + if (pdata->args.encOnTheFly) + pthread_join(sound_encode_t, NULL); + else + pthread_join(sound_cache_t, NULL); + } else + fprintf(stderr,".."); + + fprintf(stderr,"."); + + //Now that we are done with recording we cancel the timer + pdata->timer_alive = 0; + pthread_join(timer_t,NULL); +} diff --git a/src/rmd_threads.h b/src/rmd_threads.h new file mode 100644 index 0000000..e2c103a --- /dev/null +++ b/src/rmd_threads.h @@ -0,0 +1,43 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMDTHREADS_H +#define RMDTHREADS_H 1 + +#include "rmd_types.h" + + +/** +* Launch and wait capture threads. +* Also creates and waits the encoding threads when +* encode-on-the-fly is enabled. +* +* \param pdata ProgData struct containing all program data +*/ +void rmdThreads(ProgData *pdata); + + +#endif diff --git a/src/rmd_timer.c b/src/rmd_timer.c new file mode 100644 index 0000000..b8161ca --- /dev/null +++ b/src/rmd_timer.c @@ -0,0 +1,81 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_timer.h" + +#include "rmd_types.h" + +#include <pthread.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + + +void *rmdTimer(ProgData *pdata){ + long unsigned int secs_tw=1/pdata->args.fps; + long unsigned int usecs_tw=(1000000)/pdata->args.fps- secs_tw*1000000; + + while (pdata->timer_alive) { + + pthread_mutex_lock(&pdata->pause_mutex); + if (pdata->pause_state_changed) { + pdata->pause_state_changed = FALSE; + + if (!pdata->paused) { + pdata->paused = TRUE; + fprintf(stderr,"STATE:PAUSED\n");fflush(stderr); + } else{ + pdata->paused = FALSE; + fprintf(stderr,"STATE:RECORDING\n");fflush(stderr); + pthread_cond_broadcast(&pdata->pause_cond); + } + } + + if (!pdata->paused) { + pthread_mutex_unlock(&pdata->pause_mutex); + + /* FIXME TODO: detect dropped frames by delta between {time,capture}_frameno */ + pdata->frames_total++; + } else + pthread_mutex_unlock(&pdata->pause_mutex); + + pthread_mutex_lock(&pdata->time_mutex); + pdata->time_frameno++; + pthread_mutex_unlock(&pdata->time_mutex); + pthread_cond_signal(&pdata->time_cond); + + /* FIXME use nanosleep */ + if (secs_tw) + sleep(secs_tw); + + usleep(usecs_tw); + } + + pthread_exit(&errno); +} diff --git a/src/rmd_timer.h b/src/rmd_timer.h new file mode 100644 index 0000000..d9292d9 --- /dev/null +++ b/src/rmd_timer.h @@ -0,0 +1,44 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_TIMER_H +#define RMD_TIMER_H 1 + +#include "rmd_types.h" + + +/** +* Loop ,signal timer cond var,sleep-\ +* ^ | +* |________________________________/ +* +* +* \param pdata ProgData struct containing all program data +*/ +void *rmdTimer(ProgData *pdata); + + +#endif diff --git a/src/rmd_types.h b/src/rmd_types.h new file mode 100644 index 0000000..6018955 --- /dev/null +++ b/src/rmd_types.h @@ -0,0 +1,366 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMDTYPES_H +#define RMDTYPES_H 1 + +#include "config.h" + +#include <stdio.h> +#include <pthread.h> +#include <zlib.h> +#include <X11/Xlib.h> +#include <X11/extensions/XShm.h> +#include <theora/theora.h> +#include <vorbis/codec.h> +#include <vorbis/vorbisenc.h> +#include <ogg/ogg.h> + +#ifdef HAVE_LIBASOUND + #include <alsa/asoundlib.h> + + #define DEFAULT_AUDIO_DEVICE "hw:0,0" +#else + #include <sys/ioctl.h> + #include <sys/soundcard.h> + + #define DEFAULT_AUDIO_DEVICE "/dev/dsp" +#endif + +#ifdef HAVE_LIBJACK + #include <jack/jack.h> + #include <jack/ringbuffer.h> +#endif + + +//this type exists only +//for comparing the planes at caching. +//u_int64_t mught not be available everywhere. +//The performance gain comes from casting the unsigned char +//buffers to this type before comparing the two blocks. +//This is made possible by the fact that blocks +//for the Y plane are 16 bytes in width and blocks +//for the U,V planes are 8 bytes in width +#ifdef HAVE_U_INT64_T +typedef u_int64_t cmp_int_t; +#else +typedef u_int32_t cmp_int_t; +#endif + +//type of pixel proccessing for the Cb,Cr planes +//when converting from full rgb to 4:2:2 Ycbcr +enum{ + __PXL_DISCARD, //only select 1 pixel in every block of four + __PXL_AVERAGE //calculate the average of all four pixels +}; + +// Boolean type +typedef int boolean; +#ifndef FALSE +#define FALSE (0) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#define RMD_MAX_JACK_PORTS (100) + +// Forward declarations +typedef struct _ProgData ProgData; + +typedef struct _Image { + XImage *ximage; + XShmSegmentInfo shm_info; +}Image; + +typedef struct _DisplaySpecs{ //this struct holds some basic information + int screen; //about the display,needed mostly for + unsigned int width, height; //validity checks at startup + Window root; + Visual *visual; + GC gc; + int depth; +}DisplaySpecs; + +typedef struct _RectArea{ //an area that has been damaged gets stored + XRectangle rect; //in a list comprised of structs of this type + struct _RectArea *prev,*next; +}RectArea; + +typedef struct _BRWindow{ //'basic recorded window' specs + XRectangle winrect; //window attributes + XRectangle rrect; //recorded window rect in screen space coordinates + Window windowid; //id +}BRWindow; + +//defaults in the following comment lines may be out of sync with reality +//check the SetupDefaultArgs() function further bellow +typedef struct _ProgArgs{ + int delay; //start up delay + Window windowid; //window to record(default root) + char *display; //display to connect(default :0) + int x,y; //x,y offset(default 0,0) + int width,height; //defaults to window width and height + char *filename; //output file(default out.[ogg|*]) + int cursor_color; //black or white=>1 or 0 + int have_dummy_cursor; //disable/enable drawing of the dummy cursor + int xfixes_cursor; //disable/enable drawing of a cursor obtained + //through the xfixes extension + float fps; //desired framerate(default 15) + unsigned int frequency; //desired frequency (default 22050) + unsigned int channels; //no of channels(default 2) + char *device; //default sound device +#ifdef HAVE_LIBASOUND + snd_pcm_uframes_t buffsize; //buffer size(in frames) for sound capturing +#else + u_int32_t buffsize; +#endif + const char* rescue_path; + int nosound; //do not record sound(default 0) + int noshared; //do not use shared memory extension(default 0) + int nowmcheck; //do not check if there's a 3d comp window manager + //(which changes full-shots and with-shared to 1) + int full_shots; //do not poll damage, take full screenshots + int follow_mouse; //capture area follows the mouse(fullshots auto enabled) + int no_encode; //do not encode or delete the temporary files(debug opt) + int no_quick_subsample; //average pixels in chroma planes + int v_bitrate,v_quality,s_quality; //video bitrate,video-sound quality + int encOnTheFly; //encode while recording, no caching(default 0) + char *workdir; //directory to be used for cache files(default $HOME) + char *pause_shortcut; //pause/unpause shortcut sequence(Control+Alt+p) + char *stop_shortcut; //stop shortcut sequence(Control+Alt+s) + int noframe; //don't draw a frame around the recording area + int zerocompression; //image data are always flushed uncompressed + int overwrite; //overwite a previously existing file + //(do not add a .number postfix) + int use_jack; //record audio with jack + unsigned int jack_nports; + char *jack_port_names[RMD_MAX_JACK_PORTS]; + float jack_ringbuffer_secs; +} ProgArgs; + +//this struct holds anything related to encoding AND +//writting out to file. +typedef struct _EncData{ + ogg_stream_state m_ogg_ts; //theora + ogg_stream_state m_ogg_vs; //vorbis + ogg_page m_ogg_pg; //this could be avoided since + // it is used only while initializing + ogg_packet m_ogg_pckt1; //theora stream + ogg_packet m_ogg_pckt2; //vorbis stream +//theora data + theora_state m_th_st; + theora_info m_th_inf; + theora_comment m_th_cmmnt; + yuv_buffer yuv; +//vorbis data + vorbis_info m_vo_inf; + vorbis_comment m_vo_cmmnt; + vorbis_dsp_state m_vo_dsp; + vorbis_block m_vo_block; +//our file + FILE *fp; +} EncData; + +//this struct will hold a few basic +//information, needed for caching the frames. +typedef struct _CacheData{ + char *workdir, //The directory were the project + //will be stored, while recording. + //Since this will take a lot of space, the user must be + //able to change the location. + *projname, //This is the name of the folder that + //will hold the project. + //It is rMD-session-%d where %d is the pid + //of the current proccess. + //This way, running two instances + //will not create problems + //and also, a frontend can identify + //leftovers from a possible crash + //and delete them + *specsfile, //workdir+projname+specs.txt + *imgdata, //workdir+projname+img.out.gz + *audiodata; //workdir+projname+audio.pcm + + gzFile ifp; //image data file pointer + FILE *uncifp; //uncompressed image data file pointer + + FILE *afp; //audio data file pointer + +}CacheData; + +//sound buffer +//sound keeps coming so we que it in this list +//which we then traverse +typedef struct _SndBuffer{ + signed char *data; + struct _SndBuffer *next; +}SndBuffer; + +#ifdef HAVE_LIBJACK +typedef struct _JackData{ + ProgData *pdata; //pointer to prog data + jack_client_t *client; + unsigned int buffersize, //buffer size for every port in frames. + frequency, //samplerate with which jack server was started. + nports; //number of ports. + float ringbuffer_secs; + char **port_names; //names of ports(as specified in args). + jack_port_t **ports; //connections to thes ports. + jack_default_audio_sample_t + **portbuf; //retrieval of audio buffers. + pthread_mutex_t *sound_buffer_mutex; //mutex and cond_var + pthread_cond_t *sound_data_read; //in the pdata struct + jack_ringbuffer_t *sound_buffer; //data exchange happens through this + int capture_started; //used to hold recording in the beginning +}JackData; +#endif + +typedef struct _HotKey{ //Hold info about the shortcuts + int modnum; //modnum is the number of modifier masks + unsigned int mask[4]; //that should be checked (the initial + int key; //user requested modifier plus it's +}HotKey; //combinations with LockMask and NumLockMask). + +//this structure holds any data related to the program +//It's usage is mostly to be given as an argument to the +//threads,so they will have access to the program data, avoiding +//at the same time usage of any globals. +struct _ProgData { +/**recordMyDesktop specific structs*/ + ProgArgs args; //the program arguments + DisplaySpecs specs; //Display specific information + BRWindow brwin; //recording window + RectArea *rect_root; //the interchanging list roots for storing + //the changed regions + SndBuffer *sound_buffer; + EncData *enc_data; + CacheData *cache_data; + HotKey pause_key, //Shortcuts + stop_key; +#ifdef HAVE_LIBJACK + JackData *jdata; +#endif +/**X related info*/ + Display *dpy; //curtrent display +/** Mutexes*/ + pthread_mutex_t sound_buffer_mutex, + img_buff_ready_mutex, + theora_lib_mutex, //serializes access to th_encoding_clean w/theora_lib_clean + vorbis_lib_mutex, //serializes acces to v_encoding_clean w/vorbis_lib_clean + libogg_mutex, //libogg is not thread safe, + yuv_mutex; //this might not be needed since we only have + //one read-only and one write-only thread + //also on previous versions, + //y component was looped separately + //and then u and v so this was needed + //to avoid wrong coloring to render + //Currently this mutex only prevents + //the cursor from flickering +/**Condition Variables*/ + pthread_cond_t time_cond, //this gets a signal by the handler + //whenever it's time to get a screenshot + pause_cond, //this is blocks execution, + //when program is paused + sound_data_read, //a buffer is ready for proccessing + image_buffer_ready, //image encoding finished + theora_lib_clean, //the flush_ogg thread cannot + //procceed to creating last + vorbis_lib_clean; //packages until these two libs + //are no longer used, by other threads +/**Buffers,Flags and other vars*/ + unsigned char *dummy_pointer, //a dummy pointer to be drawn + //in every frame + //data is casted to unsigned for + //later use in YUV buffer + npxl; //this is the no pixel convention + //when drawing the dummy pointer + unsigned int periodtime,//time that a sound buffer lasts (microsecs) + frametime; //time that a frame lasts (microsecs) + Window shaped_w; //frame + int damage_event, //damage event base code + damage_error, //damage error base code + shm_opcode, //MIT-Shm opcode + dummy_p_size, //dummy pointer size,initially 16x16,always square + th_encoding_clean, //thread exit inidcator + v_encoding_clean, // >> >> + timer_alive, //determines loop of timer thread + hard_pause, //if sound device doesn't support pause + //we have to close and reopen + avd, //syncronization among audio and video + sound_framesize; //size of each sound frame + + /** Progam state vars */ + boolean running; //1 while the program is capturing/paused/encoding + boolean paused; //1 while the program is paused + boolean aborted; //1 if we should abort + boolean pause_state_changed; //1 if pause state changed + + //the following values are of no effect + //but they might be usefull later for profiling + unsigned int frames_total, //frames calculated by total time expirations + frames_lost; //the value of shame + + /* timer advances time_frameno, getframe copies time_frameno to capture_frameno + * access to both is serialized by time_{mutex,cond} + */ + unsigned int time_frameno, capture_frameno; + + pthread_mutex_t pause_mutex; + pthread_mutex_t time_mutex; + +#ifdef HAVE_LIBASOUND + snd_pcm_t *sound_handle; + snd_pcm_uframes_t periodsize; +#else + int sound_handle; + u_int32_t periodsize; +#endif +}; + + +//This is the header of every frame. +//Reconstruction will be correct only if made on +//the same platform. + +//We need the total number of blocks +//for each plane. + +//The number of the frame compared to the +//number of time expirations at the time of +//caching, will enable us to make up for lost frames. + + +typedef struct _FrameHeader{ + char frame_prefix[4]; //always FRAM + u_int32_t frameno, //number of frame(cached frames) + current_total; //number of frames that should have been + //taken at time of caching this one + u_int32_t Ynum, //number of changed blocks in the Y plane + Unum, //number of changed blocks in the U plane + Vnum; //number of changed blocks in the V plane +}FrameHeader; + +#endif + diff --git a/src/rmd_update_image.c b/src/rmd_update_image.c new file mode 100644 index 0000000..343e1bb --- /dev/null +++ b/src/rmd_update_image.c @@ -0,0 +1,95 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_update_image.h" + +#include "rmd_getzpixmap.h" +#include "rmd_yuv_utils.h" +#include "rmd_types.h" + +#include <X11/Xlibint.h> +#include <X11/extensions/shmproto.h> +#include <X11/extensions/XShm.h> + +#include <assert.h> + + +void rmdUpdateImage( Display * dpy, + yuv_buffer *yuv, + DisplaySpecs *specs, + RectArea **root, + BRWindow *brwin, + EncData *enc, + Image *image, + int noshmem, + int shm_opcode, + int no_quick_subsample){ + + RectArea *temp; + + for (temp = *root; temp; temp = temp->next) { + + /* sanity check the clipping, nothing on the reclist + * should go outside rrect + */ + assert(temp->rect.x >= brwin->rrect.x); + assert(temp->rect.y >= brwin->rrect.y); + assert(temp->rect.x + temp->rect.width <= brwin->rrect.x + brwin->rrect.width); + assert(temp->rect.y + temp->rect.height <= brwin->rrect.y + brwin->rrect.height); + + if (noshmem) { + rmdGetZPixmap( dpy, + specs->root, + image->ximage->data, + temp->rect.x, + temp->rect.y, + temp->rect.width, + temp->rect.height); + } else { + rmdGetZPixmapSHM( dpy, + specs->root, + &image->shm_info, + shm_opcode, + image->ximage->data, + temp->rect.x, + temp->rect.y, + temp->rect.width, + temp->rect.height); + } + rmdUpdateYuvBuffer( + yuv, + (unsigned char *)image->ximage->data, + NULL, + temp->rect.x - brwin->rrect.x, + temp->rect.y - brwin->rrect.y, + temp->rect.width, + temp->rect.height, + no_quick_subsample, + specs->depth + ); + } +} diff --git a/src/rmd_update_image.h b/src/rmd_update_image.h new file mode 100644 index 0000000..3057f18 --- /dev/null +++ b/src/rmd_update_image.h @@ -0,0 +1,70 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef UPDATE_IMAGE_H +#define UPDATE_IMAGE_H 1 + +#include "rmd_types.h" + +#include <X11/extensions/XShm.h> + +/** +* Retrieve and apply all changes, if xdamage is used. +* +* \param dpy Connection to the server +* +* \param yuv yuv_buffer that is to be modified +* +* \param specs DisplaySpecs struct with +* information about the display to be recorded +* +* \param root Root entry of the list with damaged areas +* +* \param brwin BRWindow struct contaning the recording window specs +* +* \param enc Encoding options +* +* \param datatemp Buffer for pixel data to be +* retrieved before placed on the yuv buffer +* +* \param noshmem don't use MIT_Shm extension +* +* \param no_quick_subsample Don't do quick subsampling +* +*/ +void rmdUpdateImage(Display * dpy, + yuv_buffer *yuv, + DisplaySpecs *specs, + RectArea **root, + BRWindow *brwin, + EncData *enc, + Image *image, + int noshmem, + int shm_opcode, + int no_quick_subsample); + + +#endif diff --git a/src/rmd_wm_check.c b/src/rmd_wm_check.c new file mode 100644 index 0000000..4934425 --- /dev/null +++ b/src/rmd_wm_check.c @@ -0,0 +1,76 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_wm_check.h" + +#include "rmd_types.h" + +#include <X11/Xatom.h> + + +char *rmdWMCheck(Display *dpy,Window root) { + Window *wm_child=NULL; + Atom nwm_atom, + utf8_string, + wm_name_atom, + rt; + unsigned long nbytes, nitems; + + char *wm_name_str=NULL; + int fmt; + + utf8_string = XInternAtom(dpy, "UTF8_STRING", False); + + nwm_atom =XInternAtom(dpy,"_NET_SUPPORTING_WM_CHECK",True); + wm_name_atom =XInternAtom(dpy,"_NET_WM_NAME",True); + + if (nwm_atom!=None && wm_name_atom!=None) { + + if (XGetWindowProperty( dpy,root,nwm_atom,0,100, + False,XA_WINDOW, + &rt,&fmt,&nitems, &nbytes, + (unsigned char **)((void*)&wm_child)) + != Success ) { + fprintf(stderr,"Error while trying to get a" + " window to identify the window manager.\n"); + } + + if ((wm_child == NULL)|| + (XGetWindowProperty(dpy,*wm_child,wm_name_atom,0,100, + False,utf8_string,&rt, + &fmt,&nitems, &nbytes, + (unsigned char **)((void*)&wm_name_str)) + !=Success)) { + fprintf(stderr,"Warning!!!\nYour window manager appears" + " to be non-compliant!\n"); + } + } + fprintf(stderr, "Your window manager appears to be %s\n\n", + ((wm_name_str!=NULL)?wm_name_str:"Unknown")); + + return wm_name_str; +} diff --git a/src/rmd_wm_check.h b/src/rmd_wm_check.h new file mode 100644 index 0000000..12f33dd --- /dev/null +++ b/src/rmd_wm_check.h @@ -0,0 +1,45 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef WM_CHECK_H +#define WM_CHECK_H 1 + +#include "rmd_types.h" + + +/** +*Check current running window manager. +* +* \param dpy Connection to the server +* +* \param root root window of the display +* +* \returns Window manager name +*/ +char *rmdWMCheck(Display *dpy,Window root); + + +#endif diff --git a/src/rmd_wm_is_compositing.c b/src/rmd_wm_is_compositing.c new file mode 100644 index 0000000..bc43282 --- /dev/null +++ b/src/rmd_wm_is_compositing.c @@ -0,0 +1,61 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_wm_is_compositing.h" + +#include "rmd_wm_check.h" +#include "rmd_types.h" + +#include <X11/Xatom.h> + +#include <stdlib.h> + + +boolean rmdWMIsCompositing( Display *dpy, int screen ) { + Window win; + Atom atom; + char buf[32]; + char *window_manager=rmdWMCheck( dpy, RootWindow( dpy, screen ) ); + + //If the wm name is queried successfully the wm is compliant (source + //http://standards.freedesktop.org/wm-spec/1.4/ar01s03.html#id2568282 ) + //in which case we will also free() the allcoated string. + + if (window_manager == NULL) + return FALSE; + else + free(window_manager); + + snprintf( buf, sizeof(buf), "_NET_WM_CM_S%d", screen); + atom = XInternAtom(dpy, buf, True); + if (atom == None) + return FALSE; + + win = XGetSelectionOwner(dpy, atom); + + return win != None; +} diff --git a/src/rmd_wm_is_compositing.h b/src/rmd_wm_is_compositing.h new file mode 100644 index 0000000..7f152ac --- /dev/null +++ b/src/rmd_wm_is_compositing.h @@ -0,0 +1,48 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMD_WM_IS_COMPOSITING_H +#define RMD_WM_IS_COMPOSITING_H 1 + +#include "rmd_types.h" + + +/** +*Check if the window manager is compositing (duh!). +* +* \param dpy Connection to the server +* +* \param screen screen number/id that the window manager runs on +* +* \returns TRUE if compositing, false otherwise or when +* the window manager doesn't support the required +* freedesktop.org hints for the test to be done +* succesfully. +*/ + +boolean rmdWMIsCompositing( Display *dpy, int screen) ; + +#endif diff --git a/src/rmd_yuv_utils.c b/src/rmd_yuv_utils.c new file mode 100644 index 0000000..1f163ae --- /dev/null +++ b/src/rmd_yuv_utils.c @@ -0,0 +1,517 @@ +/****************************************************************************** +* recordMyDesktop - rmd_yuv_utils.c * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* Copyright (C) 2008 Luca Bonavita * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#include "config.h" +#include "rmd_yuv_utils.h" + +#include "rmd_math.h" + + +// Keep these global (for performance reasons I assume). +unsigned char Yr[256], Yg[256], Yb[256], + Ur[256], Ug[256], UbVr[256], + Vg[256], Vb[256]; + +// FIXME: These globals are modified in other source files! We keep +// thsee here for now. These are the cache blocks. They need to be +// accesible in the dbuf macros +unsigned char *yblocks, + *ublocks, + *vblocks; + +void rmdMakeMatrices (void) { + int i; + + /* assuming 8-bit precision */ + float Yscale = 219.0, Yoffset = 16.0; + float Cscale = 224.0, Coffset = 128.0; + float RGBscale = 255.0; + + float r, g, b; + float yr, yg, yb; + float ur, ug, ub; + float vg, vb; /* vr intentionally missing */ + + /* as for ITU-R BT-601-6 specifications: */ + r = 0.299; + b = 0.114; + g = 1.0 - r - b; + + /* as a note, here are the coefficients + as for ITU-R BT-709 specifications: + r=0.2126; b=0.0722; g=1.0-r-b; */ + + yr = r * Yscale / RGBscale; + yg = g * Yscale / RGBscale; + yb = b * Yscale / RGBscale; + ur = ( -0.5 * r / ( 1 - b ) ) * Cscale / RGBscale; + ug = ( -0.5 * g / ( 1 - b ) ) * Cscale / RGBscale; + ub = ( 0.5 * Cscale / RGBscale); + /* vr = ub so UbVr = ub*i = vr*i */ + vg = ( -0.5 * g / ( 1 - r ) ) * Cscale / RGBscale; + vb = ( -0.5 * b / ( 1 - r ) ) * Cscale / RGBscale; + + for (i = 0; i < 256; i++) { + Yr[i] = (unsigned char) rmdRoundf( Yoffset + yr * i ); + Yg[i] = (unsigned char) rmdRoundf( yg * i ); + Yb[i] = (unsigned char) rmdRoundf( yb * i ); + + Ur[i] = (unsigned char) rmdRoundf( Coffset + ur * i ); + Ug[i] = (unsigned char) rmdRoundf( ug * i ); + UbVr[i] = (unsigned char) rmdRoundf( ub * i ); + + Vg[i] = (unsigned char) rmdRoundf( vg * i ); + Vb[i] = (unsigned char) rmdRoundf( Coffset + vb * i ); + } +} + +static inline int blocknum(int xv, int yv, int widthv, int blocksize) +{ + return ((yv/blocksize) * (widthv/blocksize) + (xv/blocksize)); +} + +/* These at least make some sense as macros since they need duplication for + * the multiple depths, so I've just moved and reformatted them here for now. + */ + +#define UPDATE_Y_PLANE( data, \ + x_tm, \ + y_tm, \ + width_tm, \ + height_tm, \ + yuv, \ + __depth__) { \ + \ + register unsigned char *yuv_Y = (yuv)->y + x_tm + y_tm * (yuv)->y_stride, \ + *_yr = Yr, *_yg = Yg, *_yb = Yb; \ + register u_int##__depth__##_t *datapi = (u_int##__depth__##_t *)data; \ + \ + for (int k = 0; k < height_tm; k++) { \ + for (int i = 0; i < width_tm; i++) { \ + register u_int##__depth__##_t t_val = *datapi; \ + \ + *yuv_Y = _yr[__RVALUE_##__depth__(t_val)] + \ + _yg[__GVALUE_##__depth__(t_val)] + \ + _yb[__BVALUE_##__depth__(t_val)]; \ + datapi++; \ + yuv_Y++; \ + } \ + \ + yuv_Y += (yuv)->y_stride - width_tm; \ + } \ +} + +//when adding the r values, we go beyond +//the (16 bit)range of the t_val variable, but we are performing +//32 bit arithmetics, so there's no problem. +//(This note is useless, I'm just adding because +//the addition of the A components in CALC_TVAL_AVG_32, +//now removed as uneeded, produced an overflow which would have caused +//color distrtion, where it one of the R,G or B components) +#define CALC_TVAL_AVG_16(t_val, datapi, datapi_next) { \ + register u_int16_t t1, t2, t3, t4; \ + \ + t1 = *datapi; \ + t2 = *(datapi + 1); \ + t3 = *datapi_next; \ + t4 = *(datapi_next + 1); \ + \ + t_val = ((((t1 & __R16_MASK) + (t2 & __R16_MASK) + \ + (t3 & __R16_MASK) + (t4 & __R16_MASK)) / 4) & __R16_MASK) + \ + ((((t1 & __G16_MASK) + (t2 & __G16_MASK)+ \ + (t3 & __G16_MASK) + (t4 & __G16_MASK)) / 4) & __G16_MASK) + \ + ((((t1 & __B16_MASK) + (t2 & __B16_MASK) + \ + (t3 & __B16_MASK) + (t4 & __B16_MASK)) / 4) & __B16_MASK); \ +} + +//the 4 most significant bytes represent the A component which +//does not need to be added on t_val, as it is always unused +#define CALC_TVAL_AVG_32(t_val, datapi, datapi_next) { \ + register unsigned int t1, t2, t3, t4; \ + \ + t1 = *datapi; \ + t2 = *(datapi + 1); \ + t3 = *datapi_next; \ + t4 = *(datapi_next + 1); \ + \ + t_val = ((((t1 & 0x00ff0000) + (t2 & 0x00ff0000) + \ + (t3 & 0x00ff0000) + (t4 & 0x00ff0000)) / 4) & 0x00ff0000) + \ + ((((t1 & 0x0000ff00) + (t2 & 0x0000ff00) + \ + (t3 & 0x0000ff00) + (t4 & 0x0000ff00)) / 4) & 0x0000ff00) + \ + ((((t1 & 0x000000ff) + (t2 & 0x000000ff) + \ + (t3 & 0x000000ff) + (t4 & 0x000000ff)) / 4) & 0x000000ff); \ +} + +#define UPDATE_A_UV_PIXEL( yuv_U, \ + yuv_V, \ + t_val, \ + datapi, \ + datapi_next, \ + _ur,_ug,_ubvr,_vg,_vb, \ + sampling, \ + __depth__) \ + \ + if (sampling == __PXL_AVERAGE) { \ + CALC_TVAL_AVG_##__depth__(t_val, datapi, datapi_next) \ + } else \ + t_val = *(datapi); \ + \ + *(yuv_U) = _ur[__RVALUE_##__depth__(t_val)] + \ + _ug[__GVALUE_##__depth__(t_val)] + \ + _ubvr[__BVALUE_##__depth__(t_val)]; \ + \ + *(yuv_V) = _ubvr[__RVALUE_##__depth__(t_val)] + \ + _vg[__GVALUE_##__depth__(t_val)] + \ + _vb[__BVALUE_##__depth__(t_val)]; + +#define UPDATE_UV_PLANES( data, \ + x_tm, \ + y_tm, \ + width_tm, \ + height_tm, \ + yuv, \ + sampling, \ + __depth__) { \ + \ + register u_int##__depth__##_t t_val; \ + register unsigned char *yuv_U = (yuv)->u + x_tm / 2 + \ + (y_tm * (yuv)->uv_width) / 2, \ + *yuv_V = (yuv)->v + x_tm / 2 + \ + (y_tm * (yuv)->uv_width) / 2, \ + *_ur = Ur, *_ug = Ug, *_ubvr = UbVr, \ + *_vg = Vg, *_vb = Vb; \ + register u_int##__depth__##_t *datapi = (u_int##__depth__##_t *)data, \ + *datapi_next = NULL; \ + int w_odd = width_tm % 2, h_odd = height_tm % 2; \ + \ + if (sampling == __PXL_AVERAGE) \ + datapi_next = datapi + width_tm; \ + \ + for (int k = 0; k < height_tm - h_odd; k += 2) { \ + for (int i = 0; i < width_tm - w_odd; i += 2) { \ + UPDATE_A_UV_PIXEL( yuv_U, \ + yuv_V, \ + t_val, \ + datapi, \ + datapi_next, \ + _ur, _ug, _ubvr, _vg, _vb, \ + sampling, \ + __depth__); \ + \ + datapi += 2; \ + if (sampling == __PXL_AVERAGE) \ + datapi_next += 2; \ + yuv_U++; \ + yuv_V++; \ + } \ + \ + yuv_U += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + yuv_V += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + \ + datapi += width_tm + w_odd; \ + if (sampling == __PXL_AVERAGE) \ + datapi_next += width_tm + w_odd; \ + } \ +} + +#define UPDATE_Y_PLANE_DBUF( data, \ + data_back, \ + x_tm, \ + y_tm, \ + width_tm, \ + height_tm, \ + yuv, \ + __depth__) { \ + \ + register u_int##__depth__##_t t_val; \ + register unsigned char *yuv_Y = (yuv)->y + x_tm + y_tm * (yuv)->y_stride, \ + *_yr = Yr, *_yg = Yg, *_yb = Yb; \ + register u_int##__depth__##_t *datapi = (u_int##__depth__##_t *)data, \ + *datapi_back = (u_int##__depth__##_t *)data_back; \ + \ + for (int k = 0; k < height_tm; k++) { \ + for (int i = 0; i < width_tm; i++) { \ + if (*datapi != *datapi_back) { \ + t_val = *datapi; \ + *yuv_Y = _yr[__RVALUE_##__depth__(t_val)] + \ + _yg[__GVALUE_##__depth__(t_val)] + \ + _yb[__BVALUE_##__depth__(t_val)]; \ + yblocks[blocknum(x_tm + i, y_tm + k, (yuv)->y_width, Y_UNIT_WIDTH)] = 1;\ + } \ + datapi++; \ + datapi_back++; \ + yuv_Y++; \ + } \ + yuv_Y += (yuv)->y_stride - width_tm; \ + } \ +} + +#define UPDATE_UV_PLANES_DBUF( data, \ + data_back, \ + x_tm, \ + y_tm, \ + width_tm, \ + height_tm, \ + yuv, \ + sampling, \ + __depth__) { \ + \ + register u_int##__depth__##_t t_val; \ + register unsigned char *yuv_U = (yuv)->u + x_tm / 2 + \ + (y_tm * (yuv)->uv_width) / 2, \ + *yuv_V = (yuv)->v + x_tm / 2 + \ + (y_tm * (yuv)->uv_width) / 2, \ + *_ur = Ur, *_ug = Ug, *_ubvr = UbVr, \ + *_vg = Vg, *_vb = Vb; \ + \ + register u_int##__depth__##_t *datapi = (u_int##__depth__##_t *)data, \ + *datapi_next = NULL, \ + *datapi_back = (u_int##__depth__##_t *)data_back, \ + *datapi_back_next = NULL; \ + int w_odd = width_tm % 2, h_odd = height_tm % 2; \ + \ + if (sampling == __PXL_AVERAGE) { \ + datapi_next = datapi + width_tm; \ + datapi_back_next = datapi_back + width_tm; \ + \ + for (int k = 0; k < height_tm - h_odd; k += 2) { \ + for (int i = 0; i < width_tm - w_odd; i += 2) { \ + if ( (*datapi != *datapi_back || \ + (*(datapi + 1) != *(datapi_back + 1)) || \ + (*datapi_next != *datapi_back_next) || \ + (*(datapi_next + 1) != *(datapi_back_next + 1)))) { \ + \ + UPDATE_A_UV_PIXEL( yuv_U, \ + yuv_V, \ + t_val, \ + datapi, \ + datapi_next, \ + _ur,_ug,_ubvr,_vg,_vb, \ + sampling, \ + __depth__); \ + \ + ublocks[blocknum(x_tm + i, y_tm + k, (yuv)->y_width, Y_UNIT_WIDTH)] = 1; \ + vblocks[blocknum(x_tm + i, y_tm + k, (yuv)->y_width, Y_UNIT_WIDTH)] = 1; \ + } \ + \ + datapi += 2; \ + datapi_back += 2; \ + datapi_next += 2; \ + datapi_back_next += 2; \ + \ + yuv_U++; \ + yuv_V++; \ + } \ + \ + yuv_U += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + yuv_V += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + \ + datapi += width_tm + w_odd; \ + datapi_back += width_tm + w_odd; \ + datapi_next += width_tm + w_odd; \ + datapi_back_next += width_tm + w_odd; \ + } \ + } else { \ + for (int k = 0; k < height_tm - h_odd; k += 2) { \ + for (int i = 0; i < width_tm - w_odd; i += 2) { \ + if ((*datapi != *datapi_back)) { \ + UPDATE_A_UV_PIXEL( yuv_U, \ + yuv_V, \ + t_val, \ + datapi, \ + datapi_next, \ + _ur, _ug, _ubvr, _vg, _vb, \ + sampling, \ + __depth__); \ + \ + ublocks[blocknum(x_tm + i, y_tm + k, (yuv)->y_width, Y_UNIT_WIDTH)] = 1; \ + vblocks[blocknum(x_tm + i, y_tm + k, (yuv)->y_width, Y_UNIT_WIDTH)] = 1; \ + } \ + \ + datapi += 2; \ + datapi_back += 2; \ + \ + yuv_U++; \ + yuv_V++; \ + } \ + \ + yuv_U += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + yuv_V += ((yuv)->y_stride - (width_tm - w_odd * 2)) >> 1; \ + \ + datapi += width_tm + w_odd; \ + datapi_back += width_tm + w_odd; \ + } \ + } \ +} + +void rmdUpdateYuvBuffer( yuv_buffer *yuv, + unsigned char *data, + unsigned char *data_back, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int sampling, + int depth) { + + if (data_back == NULL) { + switch (depth) { + case 24: + case 32: + UPDATE_Y_PLANE(data, x_tm, y_tm, width_tm, height_tm, yuv, 32); + UPDATE_UV_PLANES(data, x_tm, y_tm, width_tm, height_tm, yuv, sampling, 32); + break; + case 16: + UPDATE_Y_PLANE(data, x_tm, y_tm, width_tm, height_tm, yuv, 16); + UPDATE_UV_PLANES(data, x_tm, y_tm, width_tm, height_tm, yuv, sampling, 16); + break; + default: + assert(0); + } + } else { + switch (depth) { + case 24: + case 32: + UPDATE_Y_PLANE_DBUF(data, data_back, x_tm, y_tm, width_tm, height_tm, yuv, 32); + UPDATE_UV_PLANES_DBUF(data, data_back, x_tm, y_tm, width_tm, height_tm, yuv, sampling, 32); + break; + case 16: + UPDATE_Y_PLANE_DBUF(data, data_back, x_tm, y_tm, width_tm, height_tm, yuv, 16); + UPDATE_UV_PLANES_DBUF(data, data_back, x_tm, y_tm, width_tm, height_tm, yuv, sampling, 16); + break; + default: + assert(0); + } + } +} + +void rmdDummyPointerToYuv( yuv_buffer *yuv, + unsigned char *data_tm, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int x_offset, + int y_offset, + unsigned char no_pixel) { + + int i, k, j = 0; + int x_2 = x_tm / 2, y_2 = y_tm / 2, y_width_2 = yuv->y_width/2; + + for (k = y_offset; k < y_offset + height_tm; k++) { + for (i = x_offset; i < x_offset + width_tm; i++) { + j = k * 16 + i; + + if (data_tm[j * 4] != no_pixel) { + yuv->y[x_tm + (i - x_offset) + ((k - y_offset) + y_tm) * yuv->y_width] = + Yr[data_tm[j * 4 + __RBYTE]] + + Yg[data_tm[j * 4 + __GBYTE]] + + Yb[data_tm[j * 4 + __BBYTE]]; + + if ((k % 2) && (i % 2)) { + yuv->u[x_2 + (i - x_offset) / 2 + ((k - y_offset) / 2 + y_2) * y_width_2] = + Ur[data_tm[(k * width_tm + i) * 4 + __RBYTE]] + + Ug[data_tm[(k * width_tm + i) * 4 + __GBYTE]] + + UbVr[data_tm[(k * width_tm + i) * 4 + __BBYTE]]; + yuv->v[x_2 + (i - x_offset) / 2 + ((k - y_offset) / 2 + y_2) * y_width_2] = + UbVr[data_tm[(k * width_tm + i) * 4 + __RBYTE]] + + Vg[data_tm[(k * width_tm + i) * 4 + __GBYTE]] + + Vb[data_tm[(k * width_tm + i) * 4 + __BBYTE]] ; + } + } + } + } + +} + +static inline unsigned char avg_4_pixels( unsigned char *data_array, + int width_img, + int k_tm, + int i_tm, + int offset) +{ + + return ((data_array[(k_tm*width_img+i_tm)*RMD_ULONG_SIZE_T+offset]+ + data_array[((k_tm-1)*width_img+i_tm)*RMD_ULONG_SIZE_T+offset]+ + data_array[(k_tm*width_img+i_tm-1)*RMD_ULONG_SIZE_T+offset]+ + data_array[((k_tm-1)*width_img+i_tm-1)*RMD_ULONG_SIZE_T+offset])/4); +} + +void rmdXFixesPointerToYuv( yuv_buffer *yuv, + unsigned char *data_tm, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int x_offset, + int y_offset, + int column_discard_stride) { + + unsigned char avg0, avg1, avg2, avg3; + int x_2 = x_tm / 2, y_2 = y_tm / 2; + + for (int k = y_offset; k < y_offset + height_tm; k++) { + for (int i = x_offset;i < x_offset + width_tm; i++) { + int j = k * (width_tm + column_discard_stride) + i; + + yuv->y[x_tm + (i - x_offset) + (k + y_tm - y_offset) * yuv->y_width] = + (yuv->y[x_tm + (i - x_offset) + (k - y_offset + y_tm) * yuv->y_width] * + (UCHAR_MAX - data_tm[(j * RMD_ULONG_SIZE_T) + __ABYTE]) + + ( ( Yr[data_tm[(j * RMD_ULONG_SIZE_T) + __RBYTE]] + + Yg[data_tm[(j * RMD_ULONG_SIZE_T) + __GBYTE]] + + Yb[data_tm[(j * RMD_ULONG_SIZE_T) + __BBYTE]] ) % + ( UCHAR_MAX + 1 ) ) * + data_tm[(j * RMD_ULONG_SIZE_T) + __ABYTE]) / UCHAR_MAX; + + if ((k % 2) && (i % 2)) { + int idx = x_2 + (i - x_offset) / 2 + ((k - y_offset) / 2 + y_2) * yuv->uv_width; + + avg3 = avg_4_pixels( data_tm, + (width_tm + column_discard_stride), + k, i, __ABYTE); + avg2 = avg_4_pixels( data_tm, + (width_tm + column_discard_stride), + k, i, __RBYTE); + avg1 = avg_4_pixels( data_tm, + (width_tm + column_discard_stride), + k, i, __GBYTE); + avg0 = avg_4_pixels( data_tm, + (width_tm + column_discard_stride), + k, i, __BBYTE); + + yuv->u[idx] = + (yuv->u[idx] * (UCHAR_MAX - avg3) + + ((Ur[avg2] + Ug[avg1] + UbVr[avg0]) % (UCHAR_MAX + 1)) + * avg3) / UCHAR_MAX; + + yuv->v[idx]= + (yuv->u[idx] * (UCHAR_MAX - avg3) + + ((UbVr[avg2] + Vg[avg1] + Vb[avg0]) % (UCHAR_MAX + 1)) + * avg3) / UCHAR_MAX; + } + } + } +} diff --git a/src/rmd_yuv_utils.h b/src/rmd_yuv_utils.h new file mode 100644 index 0000000..f92471a --- /dev/null +++ b/src/rmd_yuv_utils.h @@ -0,0 +1,83 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef YUV_UTILS_H +#define YUV_UTILS_H 1 + +#include "rmd_macro.h" +#include "rmd_types.h" + + +// The macros work directly on this data (for performance reasons I +// suppose) so we keep this global +extern unsigned char Yr[256], Yg[256], Yb[256], + Ur[256], Ug[256], UbVr[256], + Vg[256], Vb[256]; + + +// We keep these global for now. FIXME: Isolate them. +extern unsigned char *yblocks, + *ublocks, + *vblocks; + + +/** +* Fill Yr,Yg,Yb,Ur,Ug.Ub,Vr,Vg,Vb arrays(globals) with values. +*/ +void rmdMakeMatrices(void); + +/* update yuv from data and optionally data_back */ +void rmdUpdateYuvBuffer( yuv_buffer *yuv, + unsigned char *data, + unsigned char *data_back, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int sampling_type, + int color_depth); + +void rmdDummyPointerToYuv( yuv_buffer *yuv, + unsigned char *data_tm, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int x_offset, + int y_offset, + unsigned char no_pixel); + +void rmdXFixesPointerToYuv( yuv_buffer *yuv, + unsigned char *data_tm, + int x_tm, + int y_tm, + int width_tm, + int height_tm, + int x_offset, + int y_offset, + int column_discard_stride); + +#endif diff --git a/src/skeleton.c b/src/skeleton.c new file mode 100644 index 0000000..77567db --- /dev/null +++ b/src/skeleton.c @@ -0,0 +1,296 @@ +/* + * skeleton.c + * author: Tahseen Mohammad + */ + +/* This file depends on WORDS_BIGENDIAN being defined to 1 if the host + * processor stores words with the most significant byte first (like Motorola + * and SPARC, unlike Intel and VAX). + * On little endian systems, WORDS_BIGENDIAN should be undefined. + * + * When using GNU Autotools, the correct value will be written into config.h + * if the autoconf macro AC_C_BIGENDIAN is called in configure.ac. + */ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include <ogg/ogg.h> + +#include "skeleton.h" + +#ifdef WIN32 +#define snprintf _snprintf +#endif + +static unsigned short +_le_16 (unsigned short s) +{ + unsigned short ret=s; +#ifdef WORDS_BIGENDIAN + ret = (s>>8) & 0x00ffU; + ret += (s<<8) & 0xff00U; +#endif + return ret; +} + +static ogg_uint32_t +_le_32 (ogg_uint32_t i) +{ + ogg_uint32_t ret=i; +#ifdef WORDS_BIGENDIAN + ret = (i>>24); + ret += (i>>8) & 0x0000ff00; + ret += (i<<8) & 0x00ff0000; + ret += (i<<24); +#endif + return ret; +} + +static ogg_int64_t +_le_64 (ogg_int64_t l) +{ + ogg_int64_t ret=l; + unsigned char *ucptr = (unsigned char *)&ret; +#ifdef WORDS_BIGENDIAN + unsigned char temp; + + temp = ucptr [0] ; + ucptr [0] = ucptr [7] ; + ucptr [7] = temp ; + + temp = ucptr [1] ; + ucptr [1] = ucptr [6] ; + ucptr [6] = temp ; + + temp = ucptr [2] ; + ucptr [2] = ucptr [5] ; + ucptr [5] = temp ; + + temp = ucptr [3] ; + ucptr [3] = ucptr [4] ; + ucptr [4] = temp ; + +#endif + return (*(ogg_int64_t *)ucptr); +} + +/* write an ogg_page to a file pointer */ +int write_ogg_page_to_file(ogg_page *og, FILE *out) { + int written; + + written = fwrite(og->header,1, og->header_len, out); + if (written > 0) + written += fwrite(og->body,1, og->body_len, out); + + return written; +} + +int add_message_header_field(fisbone_packet *fp, + char *header_key, + char *header_value) { + + /* size of both key and value + ': ' + CRLF */ + int this_message_size = strlen(header_key) + strlen(header_value) + 4; + if (fp->message_header_fields == NULL) { + fp->message_header_fields = _ogg_calloc(this_message_size+1, sizeof(char)); + } else { + int new_size = (fp->current_header_size + this_message_size+1) * sizeof(char); + fp->message_header_fields = _ogg_realloc(fp->message_header_fields, new_size); + } + snprintf(fp->message_header_fields + fp->current_header_size, + this_message_size+1, + "%s: %s\r\n", + header_key, + header_value); + fp->current_header_size += this_message_size; + + return 0; +} + +/* create a ogg_packet from a fishead_packet structure */ +int ogg_from_fishead(fishead_packet *fp,ogg_packet *op) { + + if (!fp || !op) return -1; + + memset(op, 0, sizeof(*op)); + op->packet = _ogg_calloc(FISHEAD_SIZE, sizeof(unsigned char)); + if (!op->packet) return -1; + + memset(op->packet, 0, FISHEAD_SIZE); + + memcpy (op->packet, FISHEAD_IDENTIFIER, 8); /* identifier */ + *((ogg_uint16_t*)(op->packet+8)) = _le_16 (SKELETON_VERSION_MAJOR); /* version major */ + *((ogg_uint16_t*)(op->packet+10)) = _le_16 (SKELETON_VERSION_MINOR); /* version minor */ + *((ogg_int64_t*)(op->packet+12)) = _le_64 (fp->ptime_n); /* presentationtime numerator */ + *((ogg_int64_t*)(op->packet+20)) = _le_64 (fp->ptime_d); /* presentationtime denominator */ + *((ogg_int64_t*)(op->packet+28)) = _le_64 (fp->btime_n); /* basetime numerator */ + *((ogg_int64_t*)(op->packet+36)) = _le_64 (fp->btime_d); /* basetime denominator */ + /* TODO: UTC time, set to zero for now */ + + op->b_o_s = 1; /* its the first packet of the stream */ + op->e_o_s = 0; /* its not the last packet of the stream */ + op->bytes = FISHEAD_SIZE; /* length of the packet in bytes */ + + return 0; +} + +/* create a ogg_packet from a fisbone_packet structure. + * call this method after the fisbone_packet is filled and all message header fields are added + * by calling add_message_header_field method. + */ +int ogg_from_fisbone(fisbone_packet *fp,ogg_packet *op) { + + int packet_size; + + if (!fp || !op) return -1; + + packet_size = FISBONE_SIZE + fp->current_header_size; + + memset (op, 0, sizeof (*op)); + op->packet = _ogg_calloc (packet_size, sizeof(unsigned char)); + if (!op->packet) return -1; + + memset (op->packet, 0, packet_size); + memcpy (op->packet, FISBONE_IDENTIFIER, 8); /* identifier */ + *((ogg_uint32_t*)(op->packet+8)) = _le_32 (FISBONE_MESSAGE_HEADER_OFFSET); /* offset of the message header fields */ + *((ogg_uint32_t*)(op->packet+12)) = _le_32 (fp->serial_no); /* serialno of the respective stream */ + *((ogg_uint32_t*)(op->packet+16)) = _le_32 (fp->nr_header_packet); /* number of header packets */ + *((ogg_int64_t*)(op->packet+20)) = _le_64 (fp->granule_rate_n); /* granulrate numerator */ + *((ogg_int64_t*)(op->packet+28)) = _le_64 (fp->granule_rate_d); /* granulrate denominator */ + *((ogg_int64_t*)(op->packet+36)) = _le_64 (fp->start_granule); /* start granule */ + *((ogg_uint32_t*)(op->packet+44)) = _le_32 (fp->preroll); /* preroll, for theora its 0 */ + *(op->packet+48) = fp->granule_shift; /* granule shift */ + if (fp->message_header_fields) + memcpy((op->packet+FISBONE_SIZE), fp->message_header_fields, fp->current_header_size); + + + op->b_o_s = 0; + op->e_o_s = 0; + op->bytes = packet_size; /* size of the packet in bytes */ + + return 0; +} + +/* fills up a fishead_packet from memory */ +static int fishead_from_data (const unsigned char * data, int len, fishead_packet *fp) { + if (!data) return -1; + + if (memcmp(data, FISHEAD_IDENTIFIER, 8)) + return -1; + + fp->version_major = _le_16 (*((ogg_uint16_t*)(data+8))); /* version major */ + fp->version_minor = _le_16 (*((ogg_uint16_t*)(data+10))); /* version minor */ + fp->ptime_n = _le_64 (*((ogg_int64_t*)(data+12))); /* presentationtime numerator */ + fp->ptime_d = _le_64 (*((ogg_int64_t*)(data+20))); /* presentationtime denominator */ + fp->btime_n = _le_64 (*((ogg_int64_t*)(data+28))); /* basetime numerator */ + fp->btime_d = _le_64 (*((ogg_int64_t*)(data+36))); /* basetime denominator */ + memcpy(fp->UTC, data+44, 20); + + return 0; +} + +/* fills up a fishead_packet from a fishead ogg_packet of a skeleton bistream */ +int fishead_from_ogg (ogg_packet *op, fishead_packet *fp) { + return fishead_from_data (op->packet, op->bytes, fp); +} + +/* fills up a fishead_packet from a fishead ogg_page of a skeleton bistream */ +int fishead_from_ogg_page (const ogg_page *og, fishead_packet *fp) { + return fishead_from_data (og->body, og->body_len, fp); +} + +/* fills up a fisbone_packet from memory */ +static int fisbone_from_data (const unsigned char * data, int len, fisbone_packet *fp) { + + if (!fp) return -1; + + if (memcmp(data, FISBONE_IDENTIFIER, 8)) + return -1; + + fp->serial_no = _le_32 (*((ogg_uint32_t*)(data+12))); /* serialno of the stream represented by this fisbone packet */ + fp->nr_header_packet = _le_32 (*((ogg_uint32_t*)(data+16))); /* number of header packets */ + fp->granule_rate_n = _le_64 (*((ogg_int64_t*)(data+20))); /* granulrate numerator */ + fp->granule_rate_d = _le_64 (*((ogg_int64_t*)(data+28))); /* granulrate denominator */ + fp->start_granule = _le_64 (*((ogg_int64_t*)(data+36))); /* start granule */ + fp->preroll = _le_32 (*((ogg_uint32_t*)(data+44))); /* preroll, for theora its 0 */ + fp->granule_shift = *(data+48); /* granule shift */ + fp->current_header_size = len - FISBONE_SIZE; + fp->message_header_fields = _ogg_calloc(fp->current_header_size+1, sizeof(char)); + if (!fp->message_header_fields) return -1; + memcpy(fp->message_header_fields, data+FISBONE_SIZE, fp->current_header_size); + + return 0; +} + +/* fills up a fisbone_packet from a fisbone ogg_packet of a skeleton bitstream */ +int fisbone_from_ogg (ogg_packet *op, fisbone_packet *fp) { + return fisbone_from_data (op->packet, op->bytes, fp); +} + +/* fills up a fisbone_packet from a fisbone ogg_page of a skeleton bistream */ +int fisbone_from_ogg_page (const ogg_page *og, fisbone_packet *fp) { + return fisbone_from_data (og->body, og->body_len, fp); +} + +int fisbone_clear(fisbone_packet *fp) +{ + if (!fp) return -1; + _ogg_free(fp->message_header_fields); + return 0; +} + +int add_fishead_to_stream(ogg_stream_state *os, fishead_packet *fp) { + + ogg_packet op; + int ret; + + ret = ogg_from_fishead(fp, &op); + if (ret<0) return ret; + ogg_stream_packetin(os, &op); + _ogg_free(op.packet); + + return 0; +} + +int add_fisbone_to_stream(ogg_stream_state *os, fisbone_packet *fp) { + + ogg_packet op; + int ret; + + ret = ogg_from_fisbone(fp, &op); + if (ret<0) return ret; + ogg_stream_packetin(os, &op); + _ogg_free(op.packet); + + return 0; +} + +int add_eos_packet_to_stream(ogg_stream_state *os) { + + ogg_packet op; + + memset (&op, 0, sizeof(op)); + op.e_o_s = 1; + ogg_stream_packetin(os, &op); + + return 0; +} + +int flush_ogg_stream_to_file(ogg_stream_state *os, FILE *out) { + + ogg_page og; + int result; + + while((result = ogg_stream_flush(os, &og))) + { + if(!result) break; + result = write_ogg_page_to_file(&og, out); + if(result != og.header_len + og.body_len) + return 1; + } + + return 0; +} diff --git a/src/skeleton.h b/src/skeleton.h new file mode 100644 index 0000000..d088e3e --- /dev/null +++ b/src/skeleton.h @@ -0,0 +1,78 @@ +/* + * skeleton.h + * author: Tahseen Mohammad + */ + +#ifndef _SKELETON_H +#define _SKELETON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdio.h> +#include <ogg/ogg.h> + +#define SKELETON_VERSION_MAJOR 3 +#define SKELETON_VERSION_MINOR 0 +#define FISHEAD_IDENTIFIER "fishead\0" +#define FISBONE_IDENTIFIER "fisbone\0" +#define FISHEAD_SIZE 64 +#define FISBONE_SIZE 52 +#define FISBONE_MESSAGE_HEADER_OFFSET 44 + +/* fishead_packet holds a fishead header packet. */ +typedef struct { + ogg_uint16_t version_major; /* skeleton version major */ + ogg_uint16_t version_minor; /* skeleton version minor */ + /* Start time of the presentation + * For a new stream presentationtime & basetime is same. */ + ogg_int64_t ptime_n; /* presentation time numerator */ + ogg_int64_t ptime_d; /* presentation time denominator */ + ogg_int64_t btime_n; /* basetime numerator */ + ogg_int64_t btime_d; /* basetime denominator */ + /* will holds the time of origin of the stream, a 20 bit field. */ + unsigned char UTC[20]; +} fishead_packet; + +/* fisbone_packet holds a fisbone header packet. */ +typedef struct { + ogg_uint32_t serial_no; /* serial no of the corresponding stream */ + ogg_uint32_t nr_header_packet; /* number of header packets */ + /* granule rate is the temporal resolution of the logical bitstream */ + ogg_int64_t granule_rate_n; /* granule rate numerator */ + ogg_int64_t granule_rate_d; /* granule rate denominator */ + ogg_int64_t start_granule; /* start granule value */ + ogg_uint32_t preroll; /* preroll */ + unsigned char granule_shift; /* 1 byte value holding the granule shift */ + char *message_header_fields; /* holds all the message header fields */ + /* current total size of the message header fields, for realloc purpose, initially zero */ + ogg_uint32_t current_header_size; +} fisbone_packet; + +extern int write_ogg_page_to_file(ogg_page *og, FILE *out); +extern int add_message_header_field(fisbone_packet *fp, char *header_key, char *header_value); +/* remember to deallocate the returned ogg_packet properly */ +extern int ogg_from_fishead(fishead_packet *fp,ogg_packet *op); +extern int ogg_from_fisbone(fisbone_packet *fp,ogg_packet *op); +extern int fisbone_clear(fisbone_packet *fp); +extern int fishead_from_ogg(ogg_packet *op,fishead_packet *fp); +extern int fisbone_from_ogg(ogg_packet *op,fisbone_packet *fp); +extern int fishead_from_ogg_page(const ogg_page *og,fishead_packet *fp); +extern int fisbone_from_ogg_page(const ogg_page *og,fisbone_packet *fp); +extern int add_fishead_to_stream(ogg_stream_state *os, fishead_packet *fp); +extern int add_fisbone_to_stream(ogg_stream_state *os, fisbone_packet *fp); +extern int add_eos_packet_to_stream(ogg_stream_state *os); +extern int flush_ogg_stream_to_file(ogg_stream_state *os, FILE *out); + +#ifdef __cplusplus +} +#endif + +#endif /* _SKELETON_H */ + + + + + + diff --git a/src/test-rectinsert-data.c b/src/test-rectinsert-data.c new file mode 100644 index 0000000..9540a5f --- /dev/null +++ b/src/test-rectinsert-data.c @@ -0,0 +1,382 @@ +/****************************************************************************** +* for recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2008 Martin Nordholts * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#include "config.h" +#include "test-rectinsert-data.h" + +#include "test-rectinsert-types.h" + + +// Defines to increase readability of test data +#define O FALSE +#define E TRUE + + +RectInsertTestdataEntry rectinsert_test_data[] = { + + // Test #1 + + { "Put a rect in place", + + { 2, 2, 2, 2 }, + + { O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #2 + + { "Put a rect in place and make sure it gets an even size and position", + + { 15, 2, 4, 3 }, + + { O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #3 + + { "Put a new rect within an existing rect", + + { 14, 2, 4, 2 }, + + { O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #4 + + { "Put a new rect over an existing rect and make sure it beocomes even", + + { 1, 1, 3, 3 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + + // Test #5 + + { "Put a new rect that partly covers an existing rect", + + { 10, 4, 6, 6 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #6 + + { "Put new small heighted but wide rect over an existing rect area", + + { 0, 8, 20, 2 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #7 + + { "Put new small widthed but high rect over two existing rect areas", + + { 2, 2, 2, 18 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #8 + + { "Prepare for test 10", + + { 8, 14, 4, 4 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #9 + + { "Prepare for test 10 (again)", + + { 16, 14, 2, 2 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,E,E,O,O, + + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,E,E,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,E,E, E,E,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O } }, + + + // Test #10 + + { "Put a rect that covers two separate rects", + + { 6, 12, 14, 8 }, + + { E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + E,E,E,E,O, O,O,O,O,O, O,O,O,O,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, E,E,E,E,E, E,O,O,O,O, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,O,O,O,O, O,O,O,O,O, O,O,O,O,O, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + O,O,E,E,O, O,E,E,E,E, E,E,E,E,E, E,E,E,E,E } }, + + + // Test #11 + + { "Cover the whole area", + + { 0, 0, 20, 20 }, + + { E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, + E,E,E,E,E, E,E,E,E,E, E,E,E,E,E, E,E,E,E,E } }, + + + // Mark end of tests + + { NULL, } +}; + diff --git a/src/test-rectinsert-data.h b/src/test-rectinsert-data.h new file mode 100644 index 0000000..7957b30 --- /dev/null +++ b/src/test-rectinsert-data.h @@ -0,0 +1,30 @@ +/****************************************************************************** +* for recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2008 Martin Nordholts * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#ifndef TEST_RECTINSERT_DATA_H +#define TEST_RECTINSERT_DATA_H + +#include "test-rectinsert-types.h" + +extern RectInsertTestdataEntry rectinsert_test_data[]; + +#endif /* TEST_RECTINSERT_DATA_H */ diff --git a/src/test-rectinsert-types.h b/src/test-rectinsert-types.h new file mode 100644 index 0000000..b39322a --- /dev/null +++ b/src/test-rectinsert-types.h @@ -0,0 +1,41 @@ +/****************************************************************************** +* for recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2008 Martin Nordholts * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#ifndef TEST_RECTINSERT_TYPES_H +#define TEST_RECTINSERT_TYPES_H + + +#include "rmd_types.h" + + +#define STATE_WIDTH 20 +#define STATE_HEIGHT 20 + + +typedef struct RectInsertTestdataEntry { + const char *description; + XRectangle new_rect; + boolean expected_state[STATE_WIDTH * STATE_HEIGHT]; +} RectInsertTestdataEntry; + + +#endif /* TEST_RECTINSERT_TYPES_H */ diff --git a/src/test-rectinsert.c b/src/test-rectinsert.c new file mode 100644 index 0000000..71d2030 --- /dev/null +++ b/src/test-rectinsert.c @@ -0,0 +1,177 @@ +/****************************************************************************** +* for recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2008 Martin Nordholts * +* * +* This program 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. * +* * +* This program 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, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +******************************************************************************/ + +#include "config.h" +#include "rmd_rectinsert.h" + +#include "test-rectinsert-types.h" +#include "test-rectinsert-data.h" +#include "rmd_types.h" + + + +static void rmdSetPixel(boolean *state, int x, int y, boolean value) { + int index = y * STATE_WIDTH + x; + + // Guard against non-optimal implementations + if (index < 0 || index >= STATE_WIDTH * STATE_HEIGHT) { + return; + } + + state[y * STATE_WIDTH + x] = value; +} + +static boolean rmdGetPixel(boolean *state, int x, int y) { + int index = y * STATE_WIDTH + x; + + // Guard against non-optimal implementations + if (index < 0 || index >= STATE_WIDTH * STATE_HEIGHT) { + return FALSE; + } + + return state[y * STATE_WIDTH + x]; +} + +static void rmdWriteGeomToState(XRectangle *rect, boolean *state) { + int x, y; + + for (y = rect->y; y < rect->y + rect->height; y++) { + for (x = rect->x; x < rect->x + rect->width; x++) { + rmdSetPixel(state, x, y, TRUE); + } + } +} + +static void rmdClearState(boolean *state) { + int x, y; + + for (y = 0; y < STATE_HEIGHT; y++) { + for (x = 0; x < STATE_WIDTH; x++) { + rmdSetPixel(state, x, y, FALSE); + } + } +} + +static void rmdWarnIfNonOptimal(XRectangle *rect) { + if (rect->x < 0 || rect->x >= STATE_WIDTH || + rect->y < 0 || rect->y >= STATE_HEIGHT || + rect->width == 0 || rect->width > STATE_WIDTH || + rect->height == 0 || rect->height > STATE_HEIGHT) + { + // The rmdRectInsert() implementation is not optimal + printf(" Non-optimal rect (and rmdRectInsert() implementation) encountered!\n" + " rect x = %d, y = %d, width = %hu, height = %hu\n", + rect->x, + rect->y, + rect->width, + rect->height); + } +} + +static void GetState(RectArea *root, boolean *state) { + RectArea *current = root; + + rmdClearState(state); + + while (current) + { + rmdWarnIfNonOptimal(¤t->rect); + + rmdWriteGeomToState(¤t->rect, state); + + current = current->next; + } +} + +static boolean rmdStatesEqual(boolean *a, boolean *b) { + int x, y; + + for (y = 0; y < STATE_HEIGHT; y++) { + for (x = 0; x < STATE_WIDTH; x++) { + if (rmdGetPixel(a, x, y) != rmdGetPixel(b, x, y)) { + return FALSE; + } + } + } + + return TRUE; +} + +static void rmdPrintState(boolean *state) { + int x, y; + + for (y = 0; y < STATE_HEIGHT; y++) { + printf(" "); + + for (x = 0; x < STATE_WIDTH; x++) { + printf(rmdGetPixel(state, x, y) ? "X" : "O"); + printf(x != STATE_WIDTH - 1 ? "," : ""); + printf((x + 1) % 5 == 0 ? " " : ""); + } + + printf("\n"); + printf((y + 1) % 5 == 0 ? "\n" : ""); + } +} + +/** + * This program tests the rmdRectInsert() functionality by calling + * rmdRectInsert() with various testdata and after each call comparing + * the current state with a predefied set of expected states. + */ +int main(int argc, char **argv) { + boolean current_state[STATE_WIDTH * STATE_HEIGHT]; + RectArea *root = NULL; + int i = 0; + int result = 0; + + printf("== Testing rmdRectInsert() ==\n"); + + // Run until there we find end of tests data + while (rectinsert_test_data[i].description != NULL) { + + printf("Test #%d: %s\n", i + 1, rectinsert_test_data[i].description); + rmdRectInsert(&root, &rectinsert_test_data[i].new_rect); + GetState(root, current_state); + + if (!rmdStatesEqual(current_state, rectinsert_test_data[i].expected_state)) { + printf(" FAILURE!\n"); + printf(" Current state:\n"); + rmdPrintState(current_state); + + printf(" Expected state:\n"); + rmdPrintState(rectinsert_test_data[i].expected_state); + + // Just set to failure and keep going... + result = -1; + } + else { + printf(" PASS\n"); + } + + // Run next test + printf("\n"); + i++; + } + + return result; +} |