diff options
Diffstat (limited to 'src/rmd_get_frame.c')
-rw-r--r-- | src/rmd_get_frame.c | 606 |
1 files changed, 363 insertions, 243 deletions
diff --git a/src/rmd_get_frame.c b/src/rmd_get_frame.c index 1a5f4d0..cd49258 100644 --- a/src/rmd_get_frame.c +++ b/src/rmd_get_frame.c @@ -120,7 +120,7 @@ static void mark_buffer_area( unsigned char *data, } } -//besides taking the first screenshot, this functions primary purpose is to +//besides taking the first screenshot, this functions primary purpose is to //initialize the structures and memory. static int rmdFirstFrame(ProgData *pdata, Image *image) { @@ -262,9 +262,140 @@ static void rmdBlocksFromList( RectArea **root, } } + +static struct timespec us_to_timespec(unsigned us) +{ + return (struct timespec){ + .tv_sec = us / 1000000, + .tv_nsec = (us % 1000000) * 1000, + }; +} + + +/* subtract t2 from t1, return difference in microseconds */ +static unsigned timespec_delta_in_us(struct timespec *t1, struct timespec *t2) +{ + unsigned us = (t1->tv_sec - t2->tv_sec) * 1000000; + + us += t1->tv_nsec / 1000; + us -= t2->tv_nsec / 1000; + + return us; +} + + +/* Synchronize the next frame with either just time, or if there's + * an audio stream, with the audio stream. + * + * Returns wether a new frame should be acquired, or if the current + * yuv contents should be reused as-is (when too far behind). + */ +static boolean sync_next_frame(ProgData *pdata) +{ + int avd, delay_us; + + assert(pdata); + + if (pdata->args.nosound) { + struct timespec now; + + /* when there's no audio timeline to synchronize with, + * drive avd entirely from CLOCK_MONOTONIC + */ + clock_gettime(CLOCK_MONOTONIC, &now); + + if (pdata->last_frame_ts.tv_sec) { + pthread_mutex_lock(&pdata->avd_mutex); + pdata->avd -= timespec_delta_in_us(&now, &pdata->last_frame_ts); + avd = pdata->avd; + pthread_mutex_unlock(&pdata->avd_mutex); + } + + pdata->last_frame_ts = now; + } + + pthread_mutex_lock(&pdata->avd_mutex); + avd = pdata->avd; + pthread_mutex_unlock(&pdata->avd_mutex); + + delay_us = (int)pdata->frametime_us + avd; + if (delay_us > 1000 /* only bother sleeping when > 1ms */) { + struct timespec delay; + + delay = us_to_timespec(delay_us); + nanosleep(&delay, NULL); + + /* update avd post-sleep */ + pthread_mutex_lock(&pdata->avd_mutex); + avd = pdata->avd; + pthread_mutex_unlock(&pdata->avd_mutex); + } + + + /* refresh the frame unless we're way too far behind */ + return (avd > 2 * -(int)MAX(pdata->frametime_us, pdata->periodtime_us)); +} + + +/* Advances *frameno according to avd, returns wether frameno was advanced. */ +static boolean advance_frameno(ProgData *pdata, unsigned *frameno) +{ + int increment, avd; + + assert(pdata); + + pthread_mutex_lock(&pdata->avd_mutex); + avd = pdata->avd; + + if (avd > (int)MAX(pdata->frametime_us, pdata->periodtime_us)) { + /* if we're far ahead, don't produce a frame */ + increment = 0; + } else if (avd > 0) { + /* if we're just a little ahead, produce a frame */ + increment = 1; + } else if (avd <= 2 * -(int)MAX(pdata->frametime_us, pdata->periodtime_us)) { + /* if we're far behind, clone as many frames as fit to ~avd=0 */ + increment = -avd / (int)pdata->frametime_us; + } else { + /* if we're just a little behind produce a frame */ + increment = 1; + } + + pdata->avd += increment * (int)pdata->frametime_us; + pthread_mutex_unlock(&pdata->avd_mutex); + + (*frameno) += increment; +#if 0 + printf("avd=%i increment=%u periodtime_us=%u frametime_us=%u\n", avd, increment, pdata->periodtime_us, pdata->frametime_us); +#endif + return (increment > 0); +} + + +static boolean paused(ProgData *pdata) +{ + 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"); + } else { + pdata->paused = FALSE; + fprintf(stderr, "STATE:RECORDING\n"); + pthread_cond_broadcast(&pdata->pause_cond); + } + } + pthread_mutex_unlock(&pdata->pause_mutex); + + return pdata->paused; +} + + /* This thread just samples the recorded window in response to rmdTimer's * triggers, updating the yuv buffer hopefully at the desired frame rate. - * Following every update, the time_frameno is propogated to capture_frameno + * Following every update, the frameno is propogated to capture_frameno * and image_buffer_ready is signaled for the cache/encode side to consume the * yuv buffer. */ @@ -280,6 +411,7 @@ void *rmdGetFrame(ProgData *pdata) Image image = {}, image_back = {}; //the image that holds //the current full screenshot int init_img1 = 0, init_img2 = 0, img_sel, d_buff; + unsigned frameno = 0; rmdThreadsSetName("rmdGetFrame"); @@ -335,284 +467,272 @@ void *rmdGetFrame(ProgData *pdata) 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 + //and arrange so that we listen for damage on all //windows rmdInitEventsPolling(pdata); while (pdata->running) { - unsigned time_frameno; - - pthread_mutex_lock(&pdata->time_mutex); - while (pdata->capture_frameno >= pdata->time_frameno) { - pthread_cond_wait(&pdata->time_cond, &pdata->time_mutex); - - if (pdata->capture_frameno >= pdata->time_frameno) { - /* *Likely* paused, but could be a spurious wakeup. - * either way, it's harmless to service the event loop - * before waiting again. This is how the timer keeps - * the event loop serviced by signaling us without - * advancing time_frameno. - */ - pthread_mutex_unlock(&pdata->time_mutex); - rmdEventLoop(pdata); - pthread_mutex_lock(&pdata->time_mutex); - } + + if (paused(pdata)) { + /* just pump events while paused, so shortcuts can work for unpausing */ + nanosleep(&(struct timespec) { .tv_nsec = 100000000 }, NULL); + rmdEventLoop(pdata); + continue; } - time_frameno = pdata->time_frameno; - pthread_mutex_unlock(&pdata->time_mutex); + if (sync_next_frame(pdata)) { + //read all events and construct list with damage + //events (if not full_shots) + rmdEventLoop(pdata); - //read all events and construct list with damage - //events (if not full_shots) - rmdEventLoop(pdata); + /* TODO: refactor this inherited spaghetti into functions */ - //switch back and front buffers (full_shots only) - if (d_buff) - img_sel = img_sel ? 0 : 1; + //switch back and front buffers (full_shots only) + if (d_buff) + img_sel = img_sel ? 0 : 1; - rmdBRWinCpy(&temp_brwin, &pdata->brwin); + rmdBRWinCpy(&temp_brwin, &pdata->brwin); - if ( pdata->args.xfixes_cursor || - pdata->args.have_dummy_cursor || - pdata->args.follow_mouse) { + 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) { + // 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); - } 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.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); + } - 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); + 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 + ); + } + } } - 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 (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 ((mouse_yoffset < 0) || (mouse_yoffset > mouse_pos_abs.height)) - mouse_yoffset = 0; + 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); + } - //draw the cursor - if ( (mouse_pos_temp.x >= 0) && - (mouse_pos_temp.y >= 0) && - (mouse_pos_temp.width > 0) && - (mouse_pos_temp.height > 0)) { + 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) { - 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, + 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, - mouse_xoffset, - mouse_yoffset, - pdata->npxl + temp_brwin.rrect.width, + pdata->specs.depth ); } - 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.xfixes_cursor) { - XFree(xcim); - xcim = NULL; - } + if (!pdata->args.full_shots) + rmdClearList(&pdata->rect_root); } - if (!pdata->args.full_shots) - rmdClearList(&pdata->rect_root); - - /* Since frame acquisition may take a long and variable time, update - * frameno again just in case we've missed some so they can be included - * as this frame by the encoder. - */ - pthread_mutex_lock(&pdata->time_mutex); - time_frameno = pdata->time_frameno; - pthread_mutex_unlock(&pdata->time_mutex); - - /* notify the encoder of the new frame */ - pthread_mutex_lock(&pdata->img_buff_ready_mutex); - pdata->capture_frameno = time_frameno; - pthread_cond_signal(&pdata->image_buffer_ready); - pthread_mutex_unlock(&pdata->img_buff_ready_mutex); - - /* XXX: note if the encoder thread doesn't manage to wake up and grab the yuv_mutex - * before this thread does in response to another tick, then the frame just - * submitted is lost and the encoder thread will wait again for another frame. - * In practice if this never happens it's irrelevant, but if it proves to be an - * issue there are options. - */ + if (advance_frameno(pdata, &frameno)) { + /* notify the encoder of additional frames */ + pthread_mutex_lock(&pdata->img_buff_ready_mutex); + pdata->capture_frameno = frameno; + pthread_cond_signal(&pdata->image_buffer_ready); + pthread_mutex_unlock(&pdata->img_buff_ready_mutex); + } } + /* Make sure the encoder/cacher doen't get lost in pthread_cond_wait indefinitely, + * now that !pdata->running they will exit their wait loops. + */ + pthread_cond_signal(&pdata->image_buffer_ready); + + /* Same for waiters on pause_cond in case we quit from being paused */ + pthread_mutex_lock(&pdata->pause_mutex); + pdata->paused = FALSE; + pthread_cond_broadcast(&pdata->pause_cond); + pthread_mutex_unlock(&pdata->pause_mutex); + if (!pdata->args.noframe) XDestroyWindow(pdata->dpy, pdata->shaped_w); |