From bb825de73ba727ba481bbe958f4eb3795bdc06aa Mon Sep 17 00:00:00 2001 From: Sean Reid Date: Sat, 10 Mar 2018 10:57:32 -0500 Subject: [PATCH 1/6] 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 --- configure.ac | 28 ++++++-- src/guacenc/Makefile.am | 14 ++-- src/guacenc/ffmpeg-compat.c | 103 ++++++++++++++++++++++++-- src/guacenc/ffmpeg-compat.h | 72 +++++++++++++++++++ src/guacenc/guacenc.c | 4 ++ src/guacenc/video.c | 139 +++++++++++++++++++++++++----------- src/guacenc/video.h | 19 ++++- 7 files changed, 320 insertions(+), 59 deletions(-) diff --git a/configure.ac b/configure.ac index dba5f715..fd2c93e0 100644 --- a/configure.ac +++ b/configure.ac @@ -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 # @@ -980,10 +998,11 @@ AC_ARG_ENABLE([guacenc], [], [enable_guacenc=yes]) -AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \ - -a "x${have_libavcodec}" = "xyes" \ - -a "x${have_libavutil}" = "xyes" \ - -a "x${have_libswscale}" = "xyes"]) +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_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} diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 8debd632..34aeaf6a 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -90,6 +90,7 @@ endif guacenc_CFLAGS = \ -Werror -Wall \ @AVCODEC_CFLAGS@ \ + @AVFORMAT_CFLAGS@ \ @AVUTIL_CFLAGS@ \ @LIBGUAC_INCLUDE@ \ @SWSCALE_CFLAGS@ @@ -97,12 +98,13 @@ guacenc_CFLAGS = \ guacenc_LDADD = \ @LIBGUAC_LTLIB@ -guacenc_LDFLAGS = \ - @AVCODEC_LIBS@ \ - @AVUTIL_LIBS@ \ - @CAIRO_LIBS@ \ - @JPEG_LIBS@ \ - @SWSCALE_LIBS@ \ +guacenc_LDFLAGS = \ + @AVCODEC_LIBS@ \ + @AVFORMAT_LIBS@ \ + @AVUTIL_LIBS@ \ + @CAIRO_LIBS@ \ + @JPEG_LIBS@ \ + @SWSCALE_LIBS@ \ @WEBP_LIBS@ EXTRA_DIST = \ diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c index c1b2c687..6f0e525b 100644 --- a/src/guacenc/ffmpeg-compat.c +++ b/src/guacenc/ffmpeg-compat.c @@ -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; +} diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h index 6f26b1d1..b88b493f 100644 --- a/src/guacenc/ffmpeg-compat.h +++ b/src/guacenc/ffmpeg-compat.h @@ -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 diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 093623a2..0bd72d5f 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -25,6 +25,7 @@ #include "parse.h" #include +#include #include #include @@ -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; diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 4592a2bb..9805a83f 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -25,6 +25,9 @@ #include #include +#ifndef AVFORMAT_AVFORMAT_H +#include +#endif #include #include #include @@ -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 */ - avcodec_close(video->context); - avcodec_free_context(&(video->context)); + if (video->context != NULL) { + avcodec_close(video->context); + avcodec_free_context(&(video->context)); + } free(video); return 0; diff --git a/src/guacenc/video.h b/src/guacenc/video.h index c1d17486..37099b13 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -26,6 +26,14 @@ #include #include +#ifndef AVCODEC_AVCODEC_H +#include +#endif + +#ifndef AVFORMAT_AVFORMAT_H +#include +#endif + #include #include @@ -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. */ From fb237d4fc984b25442cb6e4852a80b6e60cecfed Mon Sep 17 00:00:00 2001 From: Sean Reid Date: Mon, 12 Mar 2018 20:47:11 -0400 Subject: [PATCH 2/6] GUACAMOLE-465: resolved issues brought up in PR159 (unneeded dynamic mem allocation, style guide violations) --- src/guacenc/ffmpeg-compat.c | 33 ++++++++++++--------------------- src/guacenc/ffmpeg-compat.h | 13 +++---------- src/guacenc/video.c | 10 ++-------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c index 6f0e525b..632666a2 100644 --- a/src/guacenc/ffmpeg-compat.c +++ b/src/guacenc/ffmpeg-compat.c @@ -52,17 +52,14 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) { int ret; - AVPacket *pkt; - #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0) - - pkt = malloc(sizeof(AVPacket)); + AVPacket pkt; /* have to create a packet around the encoded data we have */ - av_init_packet(pkt); + av_init_packet(&pkt); if (video->context->coded_frame->pts != AV_NOPTS_VALUE) { - pkt->pts = av_rescale_q(video->context->coded_frame->pts, + pkt.pts = av_rescale_q(video->context->coded_frame->pts, video->context->time_base, video->output_stream->time_base); } @@ -70,13 +67,13 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) { 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); + pkt.data = data; + pkt.size = size; + pkt.stream_index = video->output_stream->index; + ret = av_interleaved_write_frame(video->container_format_context, &pkt); #else + AVPacket *pkt; /* 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); @@ -197,16 +194,10 @@ 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) { +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; diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h index b88b493f..b6a95e28 100644 --- a/src/guacenc/ffmpeg-compat.h +++ b/src/guacenc/ffmpeg-compat.h @@ -113,16 +113,9 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); * 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); +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 diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 9805a83f..3f7fdc9b 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -80,14 +80,8 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, /* Retrieve encoding context */ AVCodecContext* avcodec_context = - guacenc_build_avcodeccontext(video_stream, - codec, - bitrate, - width, - height, - /*gop size*/ 10, - /*qmax*/ 31, - /*qmin*/ 2, + 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 }); From fe0658dd36e20eb4f6efa0f83297c06cfc0965ef Mon Sep 17 00:00:00 2001 From: Sean Reid Date: Sat, 1 Feb 2020 22:53:38 -0500 Subject: [PATCH 3/6] GUACAMOLE-465: resolved issues brought up in PR159 and added compatibility with recent versions of libavcodec --- src/guacenc/ffmpeg-compat.c | 12 ++++------ src/guacenc/ffmpeg-compat.h | 47 +++++++++++++++++++++++++++---------- src/guacenc/guacenc.c | 3 ++- src/guacenc/video.c | 4 ++-- src/guacenc/video.h | 3 ++- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c index 632666a2..593c7b1e 100644 --- a/src/guacenc/ffmpeg-compat.c +++ b/src/guacenc/ffmpeg-compat.c @@ -55,7 +55,7 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) { #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0) AVPacket pkt; - /* have to create a packet around the encoded data we have */ + /* Have to create a packet around the encoded data we have */ av_init_packet(&pkt); if (video->context->coded_frame->pts != AV_NOPTS_VALUE) { @@ -207,9 +207,7 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, 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 +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(55, 44, 100) stream->time_base = time_base; #endif return stream->codec; @@ -234,12 +232,10 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, int guacenc_open_avcodec(AVCodecContext *avcodec_context, AVCodec *codec, AVDictionary **options, AVStream* stream) { - int ret = 0; - ret = avcodec_open2(avcodec_context, codec, options); + int 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, + int codecpar_ret = avcodec_parameters_from_context(stream->codecpar, avcodec_context); if (codecpar_ret < 0) { return codecpar_ret; diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h index b6a95e28..e0542a43 100644 --- a/src/guacenc/ffmpeg-compat.h +++ b/src/guacenc/ffmpeg-compat.h @@ -52,6 +52,16 @@ #define av_packet_unref av_free_packet #endif +/* For libavcodec <= 56.41.100: Global header flag didn't have AV_ prefix. + * Guacenc defines its own flag here to avoid conflicts with libavcodec + * macros. + */ +#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(56,41,100) +#define GUACENC_FLAG_GLOBAL_HEADER CODEC_FLAG_GLOBAL_HEADER +#else +#define GUACENC_FLAG_GLOBAL_HEADER AV_CODEC_FLAG_GLOBAL_HEADER +#endif + /* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */ #if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0) #define AV_PIX_FMT_RGB32 PIX_FMT_RGB32 @@ -80,7 +90,9 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); /** * Creates and sets up the AVCodecContext for the appropriate version - * of libavformat installed + * of libavformat installed. The AVCodecContext will be built, but + * the AVStream will also be affected by having its time_base field + * set to the value passed into this function. * * @param stream * The open AVStream @@ -112,6 +124,9 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); * @param time_base * The target time base for the encoded video * + * @return + * The pointer to the configured AVCodecContext + * */ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, int bitrate, int width, int height, int gop_size, int qmax, int qmin, @@ -125,19 +140,25 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, * 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 + * @param avcodec_context + * The context to initialize. * - * @return zero on success, a negative value on error + * @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. + * + * @return + * Zero on success, a negative value on error */ int guacenc_open_avcodec(AVCodecContext *avcodec_context, AVCodec *codec, AVDictionary **options, diff --git a/src/guacenc/guacenc.c b/src/guacenc/guacenc.c index 0bd72d5f..7662f916 100644 --- a/src/guacenc/guacenc.c +++ b/src/guacenc/guacenc.c @@ -81,8 +81,9 @@ int main(int argc, char* argv[]) { avcodec_register_all(); #endif - /* Prepare libavformat */ +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100) av_register_all(); +#endif /* Track number of overall failures */ int total_files = argc - optind; diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 3f7fdc9b..2dd41f16 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -91,9 +91,9 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, goto fail_context; } - //if format needs global headers, write them + /* If format needs global headers, write them */ if (container_format_context->oformat->flags & AVFMT_GLOBALHEADER) { - avcodec_context->flags |= CODEC_FLAG_GLOBAL_HEADER; + avcodec_context->flags |= GUACENC_FLAG_GLOBAL_HEADER; } /* Open codec for use */ diff --git a/src/guacenc/video.h b/src/guacenc/video.h index 37099b13..ea0c1615 100644 --- a/src/guacenc/video.h +++ b/src/guacenc/video.h @@ -51,7 +51,8 @@ typedef struct guacenc_video { /** * AVStream for video output. - * Persists via the AVFormatContext. + * Frames sent to this stream are written into + * the output file in the specified container format. */ AVStream* output_stream; From 81601d99fe3130fa0bdf84489b01c11ba9e9cc59 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Jun 2020 15:40:04 -0700 Subject: [PATCH 4/6] GUACAMOLE-465: Reformat/reflow comments to match established style. --- src/guacenc/ffmpeg-compat.h | 53 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h index e0542a43..a5cfb2f5 100644 --- a/src/guacenc/ffmpeg-compat.h +++ b/src/guacenc/ffmpeg-compat.h @@ -89,43 +89,43 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); /** - * Creates and sets up the AVCodecContext for the appropriate version - * of libavformat installed. The AVCodecContext will be built, but - * the AVStream will also be affected by having its time_base field - * set to the value passed into this function. + * Creates and sets up the AVCodecContext for the appropriate version of + * libavformat installed. The AVCodecContext will be built, but the AVStream + * will also be affected by having its time_base field set to the value passed + * into this function. * * @param stream - * The open AVStream + * The open AVStream. * * @param codec - * The codec used on the AVStream + * The codec used on the AVStream. * * @param bitrate * The target bitrate for the encoded video * * @param width - * The target width for the encoded video + * The target width for the encoded video. * * @param height - * The target width for the encoded video + * The target height for the encoded video. * * @param gop_size - * The size of the Group of Pictures + * The size of the Group of Pictures. * * @param qmax - * The max value of the quantizer + * The max value of the quantizer. * * @param qmin - * The min value of the quantizer + * The min value of the quantizer. * * @param pix_fmt - * The target pixel format for the encoded video + * The target pixel format for the encoded video. * * @param time_base - * The target time base for the encoded video + * The target time base for the encoded video. * * @return - * The pointer to the configured AVCodecContext + * The pointer to the configured AVCodecContext. * */ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, @@ -133,32 +133,29 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, 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(). + * 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. + * 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. + * 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. * * @return - * Zero on success, a negative value on error + * Zero on success, a negative value on error. */ int guacenc_open_avcodec(AVCodecContext *avcodec_context, AVCodec *codec, AVDictionary **options, From 9b891e2360e49c3c754bb1571e6705d653d6a68d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Tue, 23 Jun 2020 15:48:41 -0700 Subject: [PATCH 5/6] GUACAMOLE-495: Update guacenc mangpage - the MPEG-4 output is no longer a raw stream. --- src/guacenc/man/guacenc.1.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guacenc/man/guacenc.1.in b/src/guacenc/man/guacenc.1.in index 4f007875..bbfa3e1f 100644 --- a/src/guacenc/man/guacenc.1.in +++ b/src/guacenc/man/guacenc.1.in @@ -38,7 +38,7 @@ is essentially an implementation of a Guacamole client which accepts its input from files instead of a network connection, and renders directly to video instead of to the user's screen. .P -Each \fIFILE\fR specified will be encoded as a raw MPEG-4 video stream to a new +Each \fIFILE\fR specified will be encoded as MPEG-4 video to a new file named \fIFILE\fR.m4v, encoded according to the other options specified. By default, the output video will be \fI640\fRx\fI480\fR pixels, and will be saved with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be From fb94ef9e9a9bcedf2f25cd6ba8b6a0da288f5f81 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 24 Jun 2020 02:25:02 -0700 Subject: [PATCH 6/6] GUACAMOLE-495: Add whitespace and reflow as necessary to improve readability. --- src/guacenc/ffmpeg-compat.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c index 593c7b1e..db6bec63 100644 --- a/src/guacenc/ffmpeg-compat.c +++ b/src/guacenc/ffmpeg-compat.c @@ -54,7 +54,9 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) { int ret; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0) + AVPacket pkt; + /* Have to create a packet around the encoded data we have */ av_init_packet(&pkt); @@ -73,12 +75,13 @@ static int guacenc_write_packet(guacenc_video* video, void* data, int size) { ret = av_interleaved_write_frame(video->container_format_context, &pkt); #else - AVPacket *pkt; - /* we know data is already a packet if we're using a newer libavcodec */ - pkt = (AVPacket*) data; + + /* We know data is already a packet if we're using a newer libavcodec */ + AVPacket* 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 @@ -142,6 +145,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { /* For libavcodec < 57.37.100: input/output was not decoupled */ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100) + /* Write frame to video */ int got_data; if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) { @@ -155,7 +159,9 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { guacenc_write_packet(video, (void*) &packet, packet.size); av_packet_unref(&packet); } + #else + /* Write frame to video */ int result = avcodec_send_frame(video->context, frame); @@ -182,6 +188,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { av_packet_unref(&packet); } + #endif /* Frame may have been queued for later writing / reordering */ @@ -232,14 +239,16 @@ AVCodecContext* guacenc_build_avcodeccontext(AVStream* stream, AVCodec* codec, int guacenc_open_avcodec(AVCodecContext *avcodec_context, AVCodec *codec, AVDictionary **options, AVStream* stream) { + int ret = avcodec_open2(avcodec_context, codec, options); + #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100) - /* copy stream parameters to the muxer */ - int codecpar_ret = avcodec_parameters_from_context(stream->codecpar, - avcodec_context); - if (codecpar_ret < 0) { + /* Copy stream parameters to the muxer */ + int codecpar_ret = avcodec_parameters_from_context(stream->codecpar, avcodec_context); + if (codecpar_ret < 0) return codecpar_ret; - } #endif + return ret; + }