GUAC-236: Abstract video encoding process as guacenc_video struct. Move PNG-writing stub therein.
This commit is contained in:
parent
09a4f4da39
commit
19ac6e8286
@ -33,7 +33,8 @@ noinst_HEADERS = \
|
||||
jpeg.h \
|
||||
layer.h \
|
||||
log.h \
|
||||
png.h
|
||||
png.h \
|
||||
video.h
|
||||
|
||||
guacenc_SOURCES = \
|
||||
buffer.c \
|
||||
@ -63,7 +64,8 @@ guacenc_SOURCES = \
|
||||
jpeg.c \
|
||||
layer.c \
|
||||
log.c \
|
||||
png.c
|
||||
png.c \
|
||||
video.c
|
||||
|
||||
# Compile WebP support if available
|
||||
if ENABLE_WEBP
|
||||
|
@ -24,14 +24,12 @@
|
||||
#include "display.h"
|
||||
#include "layer.h"
|
||||
#include "log.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
#include <guacamole/client.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) {
|
||||
@ -53,13 +51,12 @@ int guacenc_display_sync(guacenc_display* display, guac_timestamp timestamp) {
|
||||
guacenc_layer* def_layer = guacenc_display_get_layer(display, 0);
|
||||
assert(def_layer != NULL);
|
||||
|
||||
/* STUB: Write frame as PNG */
|
||||
char filename[256];
|
||||
sprintf(filename, "frame-%" PRId64 ".png", timestamp);
|
||||
cairo_surface_t* surface = def_layer->frame->surface;
|
||||
if (surface != NULL)
|
||||
cairo_surface_write_to_png(surface, filename);
|
||||
/* Update video timeline */
|
||||
if (guacenc_video_advance_timeline(display->output, timestamp))
|
||||
return 1;
|
||||
|
||||
/* Prepare frame for write upon next flush */
|
||||
guacenc_video_prepare_frame(display->output, def_layer->frame);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "display.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
@ -85,7 +86,17 @@ cairo_operator_t guacenc_display_cairo_operator(guac_composite_mode mask) {
|
||||
}
|
||||
|
||||
guacenc_display* guacenc_display_alloc() {
|
||||
return (guacenc_display*) calloc(1, sizeof(guacenc_display));
|
||||
|
||||
/* Allocate display */
|
||||
guacenc_display* display =
|
||||
(guacenc_display*) calloc(1, sizeof(guacenc_display));
|
||||
|
||||
/* STUB: Prepare video encoding */
|
||||
display->output = guacenc_video_alloc("/tmp/test.mpg",
|
||||
640, 480, 25, 400000);
|
||||
|
||||
return display;
|
||||
|
||||
}
|
||||
|
||||
int guacenc_display_free(guacenc_display* display) {
|
||||
@ -96,6 +107,9 @@ int guacenc_display_free(guacenc_display* display) {
|
||||
if (display == NULL)
|
||||
return 0;
|
||||
|
||||
/* Finalize video */
|
||||
int retval = guacenc_video_free(display->output);
|
||||
|
||||
/* Free all buffers */
|
||||
for (i = 0; i < GUACENC_DISPLAY_MAX_BUFFERS; i++)
|
||||
guacenc_buffer_free(display->buffers[i]);
|
||||
@ -109,7 +123,7 @@ int guacenc_display_free(guacenc_display* display) {
|
||||
guacenc_image_stream_free(display->image_streams[i]);
|
||||
|
||||
free(display);
|
||||
return 0;
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "buffer.h"
|
||||
#include "image-stream.h"
|
||||
#include "layer.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <guacamole/protocol.h>
|
||||
#include <guacamole/timestamp.h>
|
||||
@ -84,6 +85,11 @@ typedef struct guacenc_display {
|
||||
*/
|
||||
guac_timestamp last_sync;
|
||||
|
||||
/**
|
||||
* The video that this display is recording to.
|
||||
*/
|
||||
guacenc_video* output;
|
||||
|
||||
} guacenc_display;
|
||||
|
||||
/**
|
||||
|
132
src/guacenc/video.c
Normal file
132
src/guacenc/video.c
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Glyptodon, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
#include "video.h"
|
||||
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
guacenc_video* guacenc_video_alloc(const char* path, int width, int height,
|
||||
int framerate, int bitrate) {
|
||||
|
||||
/* Allocate video structure */
|
||||
guacenc_video* video = malloc(sizeof(guacenc_video));
|
||||
if (video == NULL)
|
||||
return NULL;
|
||||
|
||||
/* Init properties of video */
|
||||
video->width = width;
|
||||
video->height = height;
|
||||
video->frame_duration = 1000 / framerate;
|
||||
video->bitrate = bitrate;
|
||||
|
||||
/* No frames have been written or prepared yet */
|
||||
video->last_timestamp = 0;
|
||||
video->current_time = 0;
|
||||
video->next_frame = NULL;
|
||||
|
||||
return video;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the frame previously specified by guacenc_video_prepare_frame() as a
|
||||
* new frame of video, updating the internal video timestamp by one frame's
|
||||
* worth of time.
|
||||
*
|
||||
* @param video
|
||||
* The video to flush.
|
||||
*
|
||||
* @return
|
||||
* Zero if flushing was successful, non-zero if an error occurs.
|
||||
*/
|
||||
static int guacenc_video_flush_frame(guacenc_video* video) {
|
||||
|
||||
/* Ignore empty frames */
|
||||
guacenc_buffer* buffer = video->next_frame;
|
||||
if (buffer == NULL)
|
||||
return 0;
|
||||
|
||||
/* STUB: Write frame as PNG */
|
||||
char filename[256];
|
||||
sprintf(filename, "frame-%012" PRId64 ".png", video->current_time);
|
||||
cairo_surface_t* surface = buffer->surface;
|
||||
if (surface != NULL)
|
||||
cairo_surface_write_to_png(surface, filename);
|
||||
|
||||
/* Update internal timestamp */
|
||||
video->current_time += video->frame_duration;
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
int guacenc_video_advance_timeline(guacenc_video* video,
|
||||
guac_timestamp timestamp) {
|
||||
|
||||
/* Flush frames as necessary if previously updated */
|
||||
if (video->last_timestamp != 0) {
|
||||
|
||||
/* Calculate the number of frames that should have been written */
|
||||
int elapsed = (timestamp - video->last_timestamp)
|
||||
/ video->frame_duration;
|
||||
|
||||
/* Keep previous timestamp if insufficient time has elapsed */
|
||||
if (elapsed == 0)
|
||||
return 0;
|
||||
|
||||
/* Flush frames to bring timeline in sync, duplicating if necessary */
|
||||
do {
|
||||
guacenc_video_flush_frame(video);
|
||||
} while (--elapsed != 0);
|
||||
|
||||
}
|
||||
|
||||
/* Update timestamp */
|
||||
video->last_timestamp = timestamp;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) {
|
||||
video->next_frame = buffer;
|
||||
}
|
||||
|
||||
int guacenc_video_free(guacenc_video* video) {
|
||||
|
||||
/* Ignore NULL video */
|
||||
if (video == NULL)
|
||||
return 0;
|
||||
|
||||
/* Write final frame */
|
||||
guacenc_video_flush_frame(video);
|
||||
|
||||
free(video);
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
173
src/guacenc/video.h
Normal file
173
src/guacenc/video.h
Normal file
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Glyptodon, Inc.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GUACENC_VIDEO_H
|
||||
#define GUACENC_VIDEO_H
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#include <guacamole/timestamp.h>
|
||||
|
||||
/**
|
||||
* A video which is actively being encoded. Frames can be added to the video
|
||||
* as they are generated, along with their associated timestamps, and the
|
||||
* corresponding video will be continuously written as it is encoded.
|
||||
*/
|
||||
typedef struct guacenc_video {
|
||||
|
||||
/**
|
||||
* The width of the video, in pixels.
|
||||
*/
|
||||
int width;
|
||||
|
||||
/**
|
||||
* The height of the video, in pixels.
|
||||
*/
|
||||
int height;
|
||||
|
||||
/**
|
||||
* The duration of a single frame in milliseconds.
|
||||
*/
|
||||
int frame_duration;
|
||||
|
||||
/**
|
||||
* The desired output bitrate of the video, in bits per second.
|
||||
*/
|
||||
int bitrate;
|
||||
|
||||
/**
|
||||
* A pointer to the buffer containing the most recent frame submitted via
|
||||
* guacenc_video_prepare_frame(). This buffer MUST not be freed prior to
|
||||
* the call to guacenc_video_free().
|
||||
*/
|
||||
guacenc_buffer* next_frame;
|
||||
|
||||
/**
|
||||
* The timestamp associated with the last frame, or 0 if no frames have yet
|
||||
* been added.
|
||||
*/
|
||||
guac_timestamp last_timestamp;
|
||||
|
||||
/**
|
||||
* The relative position within the current video timeline, where 0 is the
|
||||
* first frame of video, in milliseconds. This value will be incremented as
|
||||
* frames are output.
|
||||
*/
|
||||
guac_timestamp current_time;
|
||||
|
||||
} guacenc_video;
|
||||
|
||||
/**
|
||||
* Allocates a new guacenc_video which encodes video according to the given
|
||||
* specifications, saving the output in the given file. The output file will be
|
||||
* created if necessary and truncated if it already exists. Frames will be
|
||||
* scaled up or down as necessary to fit the given width and height. Note that
|
||||
* frames written to this guacenc_video may be buffered, and are not guaranteed
|
||||
* to be written until guacenc_video_free() is called.
|
||||
*
|
||||
* @param path
|
||||
* The full path to the file in which encoded video should be written.
|
||||
*
|
||||
* @param width
|
||||
* The width of the desired video, in pixels.
|
||||
*
|
||||
* @param height
|
||||
* The height of the desired video, in pixels.
|
||||
*
|
||||
* @param framerate
|
||||
* The rate at which frames should be encoded within the video, in frames
|
||||
* per second.
|
||||
*
|
||||
* @param bitrate
|
||||
* The desired overall bitrate of the resulting encoded video, in kilobits
|
||||
* per second.
|
||||
*/
|
||||
guacenc_video* guacenc_video_alloc(const char* path, int width, int height,
|
||||
int framerate, int bitrate);
|
||||
|
||||
/**
|
||||
* Advances the timeline of the encoding process to the given timestamp, such
|
||||
* that frames added via guacenc_video_prepare_frame() will be encoded at the
|
||||
* proper frame boundaries within the video. Duplicate frames will be encoded
|
||||
* as necessary to ensure that the output is correctly timed with respect to
|
||||
* the given timestamp. This is particularly important as Guacamole does not
|
||||
* have a framerate per se, and the time between each Guacamole "frame" will
|
||||
* vary significantly.
|
||||
*
|
||||
* This function MUST be called prior to invoking guacenc_video_prepare_frame()
|
||||
* to ensure the prepared frame will be encoded at the correct point in time.
|
||||
*
|
||||
* @param video
|
||||
* The video whose timeline should be adjusted.
|
||||
*
|
||||
* @param timestamp
|
||||
* The Guacamole timestamp denoting the point in time that the video
|
||||
* timeline should be advanced to, as dictated by a parsed "sync"
|
||||
* instruction.
|
||||
*
|
||||
* @return
|
||||
* Zero if the timeline was adjusted successfully, non-zero if an error
|
||||
* occurs (such as during the encoding of duplicate frames).
|
||||
*/
|
||||
int guacenc_video_advance_timeline(guacenc_video* video,
|
||||
guac_timestamp timestamp);
|
||||
|
||||
/**
|
||||
* Stores the given buffer within the given video structure such that it will
|
||||
* be written if it falls within proper frame boundaries. If the timeline of
|
||||
* the video (as dictated by guacenc_video_advance_timeline()) is not at a
|
||||
* frame boundary with respect to the video framerate (it occurs between frame
|
||||
* boundaries), the prepared frame will only be written if another frame is not
|
||||
* prepared within the same pair of frame boundaries). The prepared frame will
|
||||
* not be written until it is implicitly flushed through updates to the video
|
||||
* timeline or through reaching the end of the encoding process
|
||||
* (guacenc_video_free()).
|
||||
*
|
||||
* Any given buffer MUST NOT be freed prior to the call to guacenc_video_free()
|
||||
* which ultimately ends the encoding process.
|
||||
*
|
||||
* @param video
|
||||
* The video in which the given buffer should be queued for possible
|
||||
* writing (depending on timing vs. video framerate).
|
||||
*
|
||||
* @param buffer
|
||||
* The guacenc_buffer representing the image data of the frame that should
|
||||
* be queued. This buffer MUST NOT be freed prior to the call to
|
||||
* guacenc_video_free() which ultimately ends the encoding process.
|
||||
*/
|
||||
void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer);
|
||||
|
||||
/**
|
||||
* Frees all resources associated with the given video, finalizing the encoding
|
||||
* process. Any buffered frames which have not yet been written will be written
|
||||
* at this point. Once this function is invoked, it is safe to resume freeing
|
||||
* any buffers provided to guacenc_video_prepare_frame().
|
||||
*
|
||||
* @return
|
||||
* Zero if the video was successfully written and freed, non-zero if the
|
||||
* video could not be written due to an error.
|
||||
*/
|
||||
int guacenc_video_free(guacenc_video* video);
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user