GUACAMOLE-465: added dependency to libavformat as first step to supporting other types of codecs and containers in guacenc. migrated existing functionality to use the libavformat library for writing the files. there is not differnce to the user with this patch, but it provides a good base to finish this new feature from later

This commit is contained in:
Sean Reid 2018-03-10 10:57:32 -05:00 committed by Sean Reid
parent 68a6285818
commit bb825de73b
7 changed files with 320 additions and 59 deletions

View File

@ -202,6 +202,24 @@ fi
AM_CONDITIONAL([ENABLE_AVCODEC], [test "x${have_libavcodec}" = "xyes"])
#
# libavformat
#
have_libavformat=disabled
AC_ARG_WITH([libavformat],
[AS_HELP_STRING([--with-libavformat],
[use libavformat when encoding video @<:@default=check@:>@])],
[].
[with_libavformat=check])
if test "x$with_libavformat" != "xno"
then
have_libavformat=yes
PKG_CHECK_MODULES([AVFORMAT], [libavformat],, [have_libavformat=no]);
fi
AM_CONDITIONAL([ENABLE_AVFORMAT], [test "x${have_libavformat}" = "xyes"])
#
# libavutil
#
@ -983,7 +1001,8 @@ AC_ARG_ENABLE([guacenc],
AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \
-a "x${have_libavcodec}" = "xyes" \
-a "x${have_libavutil}" = "xyes" \
-a "x${have_libswscale}" = "xyes"])
-a "x${have_libswscale}" = "xyes" \
-a "x${have_libavformat}" = "xyes"])
#
# guaclog
@ -1076,6 +1095,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION
freerdp2 ............ ${have_freerdp2}
pango ............... ${have_pango}
libavcodec .......... ${have_libavcodec}
libavformat.......... ${have_libavformat}
libavutil ........... ${have_libavutil}
libssh2 ............. ${have_libssh2}
libssl .............. ${have_ssl}

View File

@ -90,6 +90,7 @@ endif
guacenc_CFLAGS = \
-Werror -Wall \
@AVCODEC_CFLAGS@ \
@AVFORMAT_CFLAGS@ \
@AVUTIL_CFLAGS@ \
@LIBGUAC_INCLUDE@ \
@SWSCALE_CFLAGS@
@ -99,6 +100,7 @@ guacenc_LDADD = \
guacenc_LDFLAGS = \
@AVCODEC_LIBS@ \
@AVFORMAT_LIBS@ \
@AVUTIL_LIBS@ \
@CAIRO_LIBS@ \
@JPEG_LIBS@ \

View File

@ -51,8 +51,41 @@
*/
static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
/* Write data, logging any errors */
if (fwrite(data, 1, size, video->output) == 0) {
int ret;
AVPacket *pkt;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0)
pkt = malloc(sizeof(AVPacket));
/* have to create a packet around the encoded data we have */
av_init_packet(pkt);
if (video->context->coded_frame->pts != AV_NOPTS_VALUE) {
pkt->pts = av_rescale_q(video->context->coded_frame->pts,
video->context->time_base,
video->output_stream->time_base);
}
if (video->context->coded_frame->key_frame) {
pkt->flags |= AV_PKT_FLAG_KEY;
}
pkt->data = data;
pkt->size = size;
pkt->stream_index = video->output_stream->index;
ret = av_interleaved_write_frame(video->container_format_context, pkt);
free(pkt);
#else
/* we know data is already a packet if we're using a newer libavcodec */
pkt = (AVPacket*) data;
av_packet_rescale_ts(pkt, video->context->time_base, video->output_stream->time_base);
pkt->stream_index = video->output_stream->index;
ret = av_interleaved_write_frame(video->container_format_context, pkt);
#endif
if (ret != 0) {
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
return -1;
@ -62,8 +95,7 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
video->next_pts, size);
return 0;
return ret;
}
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
@ -123,7 +155,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
/* Write corresponding data to file */
if (got_data) {
guacenc_write_packet(video, packet.data, packet.size);
guacenc_write_packet(video, (void*) &packet, packet.size);
av_packet_unref(&packet);
}
#else
@ -149,7 +181,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
got_data = 1;
/* Attempt to write data to output file */
guacenc_write_packet(video, packet.data, packet.size);
guacenc_write_packet(video, (void*) &packet, packet.size);
av_packet_unref(&packet);
}
@ -165,3 +197,62 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
#endif
}
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream,
AVCodec* codec,
int bitrate,
int width,
int height,
int gop_size,
int qmax,
int qmin,
int pix_fmt,
AVRational time_base) {
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 33, 100)
stream->codec->bit_rate = bitrate;
stream->codec->width = width;
stream->codec->height = height;
stream->codec->gop_size = gop_size;
stream->codec->qmax = qmax;
stream->codec->qmin = qmin;
stream->codec->pix_fmt = pix_fmt;
stream->codec->time_base = time_base;
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55, 44, 100)
stream->codec->time_base = time_base;
#else
stream->time_base = time_base;
#endif
return stream->codec;
#else
AVCodecContext* context = avcodec_alloc_context3(codec);
if (context) {
context->bit_rate = bitrate;
context->width = width;
context->height = height;
context->gop_size = gop_size;
context->qmax = qmax;
context->qmin = qmin;
context->pix_fmt = pix_fmt;
context->time_base = time_base;
stream->time_base = time_base;
}
return context;
#endif
}
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
AVCodec *codec, AVDictionary **options,
AVStream* stream) {
int ret = 0;
ret = avcodec_open2(avcodec_context, codec, options);
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
int codecpar_ret = 0;
/* copy stream parameters to the muxer */
codecpar_ret = avcodec_parameters_from_context(stream->codecpar,
avcodec_context);
if (codecpar_ret < 0) {
return codecpar_ret;
}
#endif
return ret;
}

View File

@ -78,5 +78,77 @@
*/
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
/**
* Creates and sets up the AVCodecContext for the appropriate version
* of libavformat installed
*
* @param stream
* The open AVStream
*
* @param codec
* The codec used on the AVStream
*
* @param bitrate
* The target bitrate for the encoded video
*
* @param width
* The target width for the encoded video
*
* @param height
* The target width for the encoded video
*
* @param gop_size
* The size of the Group of Pictures
*
* @param qmax
* The max value of the quantizer
*
* @param qmin
* The min value of the quantizer
*
* @param pix_fmt
* The target pixel format for the encoded video
*
* @param time_base
* The target time base for the encoded video
*
*/
AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream,
AVCodec* codec,
int bitrate,
int width,
int height,
int gop_size,
int qmax,
int qmin,
int pix_fmt,
AVRational time_base);
/**
* A wrapper for avcodec_open2(). Because libavformat ver
* 57.33.100 and greater use stream->codecpar rather than
* stream->codec to handle information to the codec,
* there needs to be an additional step in that version.
* So this wrapper handles that. Otherwise, it's the
* same as avcodec_open2().
*
* @param avcodec_context The context to initialize.
* @param codec The codec to open this context for. If a non-NULL codec has
* been previously passed to avcodec_alloc_context3() or
* for this context, then this parameter MUST be either NULL or
* equal to the previously passed codec.
* @param options A dictionary filled with AVCodecContext and codec-private
* options. On return this object will be filled with options
* that were not found.
* @param stream The stream for the codec context.
* Only used in libavformat >= 57.33.100. Can be NULL in
* lower versions
*
* @return zero on success, a negative value on error
*/
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
AVCodec *codec, AVDictionary **options,
AVStream* stream);
#endif

View File

@ -25,6 +25,7 @@
#include "parse.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <getopt.h>
#include <stdbool.h>
@ -80,6 +81,9 @@ int main(int argc, char* argv[]) {
avcodec_register_all();
#endif
/* Prepare libavformat */
av_register_all();
/* Track number of overall failures */
int total_files = argc - optind;
int failures = 0;

View File

@ -25,6 +25,9 @@
#include <cairo/cairo.h>
#include <libavcodec/avcodec.h>
#ifndef AVFORMAT_AVFORMAT_H
#include <libavformat/avformat.h>
#endif
#include <libavutil/common.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
@ -43,6 +46,21 @@
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
int width, int height, int bitrate) {
AVOutputFormat *container_format;
AVFormatContext *container_format_context;
AVStream *video_stream;
int ret;
int failed_header = 0;
/* allocate the output media context */
avformat_alloc_output_context2(&container_format_context, NULL, NULL, path);
if (container_format_context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to determine container from output file name\n");
goto fail_codec;
}
container_format = container_format_context->oformat;
/* Pull codec based on name */
AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
if (codec == NULL) {
@ -51,25 +69,41 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
goto fail_codec;
}
/* create stream */
video_stream = NULL;
video_stream = avformat_new_stream(container_format_context, codec);
if (video_stream == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Could not allocate encoder stream. Cannot continue.\n");
goto fail_format_context;
}
video_stream->id = container_format_context->nb_streams - 1;
/* Retrieve encoding context */
AVCodecContext* context = avcodec_alloc_context3(codec);
if (context == NULL) {
AVCodecContext* avcodec_context =
guacenc_build_avcodeccontext(video_stream,
codec,
bitrate,
width,
height,
/*gop size*/ 10,
/*qmax*/ 31,
/*qmin*/ 2,
/*pix fmt*/ AV_PIX_FMT_YUV420P,
/*time base*/ (AVRational) { 1, GUACENC_VIDEO_FRAMERATE });
if (avcodec_context == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for "
"codec \"%s\".", codec_name);
goto fail_context;
}
/* Init context with encoding parameters */
context->bit_rate = bitrate;
context->width = width;
context->height = height;
context->time_base = (AVRational) { 1, GUACENC_VIDEO_FRAMERATE };
context->gop_size = 10;
context->max_b_frames = 1;
context->pix_fmt = AV_PIX_FMT_YUV420P;
//if format needs global headers, write them
if (container_format_context->oformat->flags & AVFMT_GLOBALHEADER) {
avcodec_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
/* Open codec for use */
if (avcodec_open2(context, codec, NULL) < 0) {
if (guacenc_open_avcodec(avcodec_context, codec, NULL, video_stream) < 0) {
guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name);
goto fail_codec_open;
}
@ -81,9 +115,9 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
}
/* Copy necessary data for frame from context */
frame->format = context->pix_fmt;
frame->width = context->width;
frame->height = context->height;
frame->format = avcodec_context->pix_fmt;
frame->width = avcodec_context->width;
frame->height = avcodec_context->height;
/* Allocate actual backing data for frame */
if (av_image_alloc(frame->data, frame->linesize, frame->width,
@ -91,31 +125,32 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
goto fail_frame_data;
}
/* Open output file */
int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
if (fd == -1) {
guacenc_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s",
path, strerror(errno));
goto fail_output_fd;
/* Open output file, if the container needs it */
if (!(container_format->flags & AVFMT_NOFILE)) {
ret = avio_open(&container_format_context->pb, path, AVIO_FLAG_WRITE);
if (ret < 0) {
guacenc_log(GUAC_LOG_ERROR, "Error occurred while opening output file.\n");
goto fail_output_avio;
}
}
/* Create stream for output file */
FILE* output = fdopen(fd, "wb");
if (output == NULL) {
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate stream for output "
"file \"%s\": %s", path, strerror(errno));
goto fail_output_file;
/* write the stream header, if needed */
ret = avformat_write_header(container_format_context, NULL);
if (ret < 0) {
guacenc_log(GUAC_LOG_ERROR, "Error occurred while writing output file header.\n");
failed_header = true;
}
/* Allocate video structure */
guacenc_video* video = malloc(sizeof(guacenc_video));
if (video == NULL) {
goto fail_video;
goto fail_output_file;
}
/* Init properties of video */
video->output = output;
video->context = context;
video->output_stream = video_stream;
video->context = avcodec_context;
video->container_format_context = container_format_context;
video->next_frame = frame;
video->width = width;
video->height = height;
@ -125,16 +160,24 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
video->last_timestamp = 0;
video->next_pts = 0;
if (failed_header) {
guacenc_log(GUAC_LOG_ERROR, "An incompatible codec/container "
"combination was specified. Cannot encode.\n");
goto fail_output_file;
}
return video;
/* Free all allocated data in case of failure */
fail_video:
fclose(output);
fail_output_file:
close(fd);
avio_close(container_format_context->pb);
/* delete the file that was created if it was actually created */
if (access(path, F_OK) != -1) {
remove(path);
}
fail_output_fd:
fail_output_avio:
av_freep(&frame->data[0]);
fail_frame_data:
@ -142,7 +185,13 @@ fail_frame_data:
fail_frame:
fail_codec_open:
avcodec_free_context(&context);
avcodec_free_context(&avcodec_context);
fail_format_context:
/* failing to write the container implicitly frees the context */
if (!failed_header) {
avformat_free_context(container_format_context);
}
fail_context:
fail_codec:
@ -435,26 +484,34 @@ 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);
/* write trailer, if needed */
if (video->container_format_context != NULL &&
video->output_stream != NULL) {
guacenc_log(GUAC_LOG_DEBUG, "Writing trailer: %d\n",
av_write_trailer(video->container_format_context) == 0 ?
"success" : "failure");
}
/* File is now completely written */
fclose(video->output);
if (video->container_format_context != NULL) {
avio_close(video->container_format_context->pb);
}
/* Free frame encoding data */
av_freep(&video->next_frame->data[0]);
av_frame_free(&video->next_frame);
/* Clean up encoding context */
if (video->context != NULL) {
avcodec_close(video->context);
avcodec_free_context(&(video->context));
}
free(video);
return 0;

View File

@ -26,6 +26,14 @@
#include <guacamole/timestamp.h>
#include <libavcodec/avcodec.h>
#ifndef AVCODEC_AVCODEC_H
#include <libavcodec/avcodec.h>
#endif
#ifndef AVFORMAT_AVFORMAT_H
#include <libavformat/avformat.h>
#endif
#include <stdint.h>
#include <stdio.h>
@ -42,9 +50,10 @@
typedef struct guacenc_video {
/**
* Output file stream.
* AVStream for video output.
* Persists via the AVFormatContext.
*/
FILE* output;
AVStream* output_stream;
/**
* The open encoding context from libavcodec, created for the codec
@ -52,6 +61,12 @@ typedef struct guacenc_video {
*/
AVCodecContext* context;
/**
* The open format context from libavformat, created for the file
* container specified when this guacenc_video was created.
*/
AVFormatContext* container_format_context;
/**
* The width of the video, in pixels.
*/