From 1ce39306cb2d66dde8be717a196cce7e9c6c9ee0 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Fri, 11 Mar 2016 15:23:03 -0800 Subject: [PATCH] GUAC-236: Do not store buffer - encode directly. Flush frames at end. --- src/guacenc/video.c | 100 +++++++++++++++++++++++++++++++++----------- src/guacenc/video.h | 27 ++++-------- 2 files changed, 82 insertions(+), 45 deletions(-) diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 8c7dd45f..fccc9204 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -94,7 +94,7 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* Init properties of video */ video->context = context; - video->frame = frame; + video->next_frame = frame; video->width = width; video->height = height; video->bitrate = bitrate; @@ -102,7 +102,6 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* No frames have been written or prepared yet */ video->last_timestamp = 0; video->next_pts = 0; - video->next_frame = NULL; return video; @@ -119,6 +118,63 @@ fail_context: } +/** + * Flushes the specied frame as a new frame of video, updating the internal + * video timestamp by one frame's worth of time. The pts member of the given + * frame structure will be updated with the current presentation timestamp of + * the video. If pending frames of the video are being flushed, the given frame + * may be NULL (as required by avcodec_encode_video2()). + * + * @param video + * The video to write the given frame to. + * + * @param frame + * The frame to write to the video, or NULL if previously-written frames + * are being flushed. + * + * @return + * A positive value if the frame was successfully written, zero if the + * frame has been saved for later writing / reordering, negative if an + * error occurs. + */ +static int guacenc_video_write_frame(guacenc_video* video, AVFrame* frame) { + + /* Init video packet */ + AVPacket packet; + av_init_packet(&packet); + + /* Request that encoder allocate data for packet */ + packet.data = NULL; + packet.size = 0; + + /* Set timestamp of frame, if frame given */ + if (frame != NULL) + frame->pts = video->next_pts; + + /* Write frame to video */ + int got_data; + if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) { + guacenc_log(GUAC_LOG_WARNING, "Error encoding frame #%" PRId64, + video->next_pts); + return -1; + } + + /* Write corresponding data to file */ + if (got_data) { + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", + video->next_pts, packet.size); + /* TODO: Write frame to file */ + av_packet_unref(&packet); + } + + /* Update presentation timestamp for next frame */ + video->next_pts++; + + /* Write was successful */ + return got_data; + +} + /** * 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 @@ -132,26 +188,8 @@ fail_context: */ static int guacenc_video_flush_frame(guacenc_video* video) { - /* Ignore empty frames */ - guacenc_buffer* buffer = video->next_frame; - if (buffer == NULL) - return 0; - - /* Init video packet */ - AVPacket packet; - av_init_packet(&packet); - - /* Request that encoder allocate data for packet */ - packet.data = NULL; - packet.size = 0; - - /* TODO: Write frame to video */ - guacenc_log(GUAC_LOG_DEBUG, "Encoding frame #%08" PRId64, video->next_pts); - - /* Update presentation timestamp for next frame */ - video->next_pts++; - - return 0; + /* Write frame to video */ + return guacenc_video_write_frame(video, video->next_frame) < 0; } @@ -183,7 +221,9 @@ int guacenc_video_advance_timeline(guacenc_video* video, } void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { - video->next_frame = buffer; + + /* TODO: Convert frame buffer to video->next_frame */ + } int guacenc_video_free(guacenc_video* video) { @@ -195,9 +235,19 @@ int guacenc_video_free(guacenc_video* video) { /* Write final frame */ guacenc_video_flush_frame(video); + /* Init video packet for final flush of encoded data */ + AVPacket packet; + av_init_packet(&packet); + + /* Flush any unwritten frames */ + int retval; + do { + retval = guacenc_video_write_frame(video, NULL); + } while (retval > 0); + /* Free frame encoding data */ - av_freep(&video->frame->data[0]); - av_frame_free(&video->frame); + av_freep(&video->next_frame->data[0]); + av_frame_free(&video->next_frame); /* Clean up encoding context */ avcodec_close(video->context); diff --git a/src/guacenc/video.h b/src/guacenc/video.h index e72af3e8..0427990e 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -49,12 +49,6 @@ typedef struct guacenc_video { */ AVCodecContext* context; - /** - * An image data area, containing YCbCr image data in the format required - * by avcodec_encode_video2(), for use and re-use as frames are rendered. - */ - AVFrame* frame; - /** * The width of the video, in pixels. */ @@ -71,11 +65,11 @@ typedef struct guacenc_video { 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(). + * An image data area containing the next frame to be written, encoded as + * YCbCr image data in the format required by avcodec_encode_video2(), for + * use and re-use as frames are rendered. */ - guacenc_buffer* next_frame; + AVFrame* next_frame; /** * The presentation timestamp that should be used for the next frame. This @@ -95,9 +89,7 @@ typedef struct 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. + * scaled up or down as necessary to fit the given width and height. * * @param path * The full path to the file in which encoded video should be written. @@ -157,25 +149,20 @@ int guacenc_video_advance_timeline(guacenc_video* 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. + * be queued. */ 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(). + * at this point. * * @return * Zero if the video was successfully written and freed, non-zero if the