GUACAMOLE-465: Merge produce MPEG-4 output within a proper container.
This commit is contained in:
commit
614f38767e
22
configure.ac
22
configure.ac
@ -202,6 +202,24 @@ fi
|
|||||||
|
|
||||||
AM_CONDITIONAL([ENABLE_AVCODEC], [test "x${have_libavcodec}" = "xyes"])
|
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
|
# libavutil
|
||||||
#
|
#
|
||||||
@ -998,7 +1016,8 @@ AC_ARG_ENABLE([guacenc],
|
|||||||
AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \
|
AM_CONDITIONAL([ENABLE_GUACENC], [test "x${enable_guacenc}" = "xyes" \
|
||||||
-a "x${have_libavcodec}" = "xyes" \
|
-a "x${have_libavcodec}" = "xyes" \
|
||||||
-a "x${have_libavutil}" = "xyes" \
|
-a "x${have_libavutil}" = "xyes" \
|
||||||
-a "x${have_libswscale}" = "xyes"])
|
-a "x${have_libswscale}" = "xyes" \
|
||||||
|
-a "x${have_libavformat}" = "xyes"])
|
||||||
|
|
||||||
#
|
#
|
||||||
# guaclog
|
# guaclog
|
||||||
@ -1091,6 +1110,7 @@ $PACKAGE_NAME version $PACKAGE_VERSION
|
|||||||
freerdp2 ............ ${have_freerdp2}
|
freerdp2 ............ ${have_freerdp2}
|
||||||
pango ............... ${have_pango}
|
pango ............... ${have_pango}
|
||||||
libavcodec .......... ${have_libavcodec}
|
libavcodec .......... ${have_libavcodec}
|
||||||
|
libavformat.......... ${have_libavformat}
|
||||||
libavutil ........... ${have_libavutil}
|
libavutil ........... ${have_libavutil}
|
||||||
libssh2 ............. ${have_libssh2}
|
libssh2 ............. ${have_libssh2}
|
||||||
libssl .............. ${have_ssl}
|
libssl .............. ${have_ssl}
|
||||||
|
@ -90,6 +90,7 @@ endif
|
|||||||
guacenc_CFLAGS = \
|
guacenc_CFLAGS = \
|
||||||
-Werror -Wall \
|
-Werror -Wall \
|
||||||
@AVCODEC_CFLAGS@ \
|
@AVCODEC_CFLAGS@ \
|
||||||
|
@AVFORMAT_CFLAGS@ \
|
||||||
@AVUTIL_CFLAGS@ \
|
@AVUTIL_CFLAGS@ \
|
||||||
@LIBGUAC_INCLUDE@ \
|
@LIBGUAC_INCLUDE@ \
|
||||||
@SWSCALE_CFLAGS@
|
@SWSCALE_CFLAGS@
|
||||||
@ -99,6 +100,7 @@ guacenc_LDADD = \
|
|||||||
|
|
||||||
guacenc_LDFLAGS = \
|
guacenc_LDFLAGS = \
|
||||||
@AVCODEC_LIBS@ \
|
@AVCODEC_LIBS@ \
|
||||||
|
@AVFORMAT_LIBS@ \
|
||||||
@AVUTIL_LIBS@ \
|
@AVUTIL_LIBS@ \
|
||||||
@CAIRO_LIBS@ \
|
@CAIRO_LIBS@ \
|
||||||
@JPEG_LIBS@ \
|
@JPEG_LIBS@ \
|
||||||
|
@ -51,8 +51,41 @@
|
|||||||
*/
|
*/
|
||||||
static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
|
static int guacenc_write_packet(guacenc_video* video, void* data, int size) {
|
||||||
|
|
||||||
/* Write data, logging any errors */
|
int ret;
|
||||||
if (fwrite(data, 1, size, video->output) == 0) {
|
|
||||||
|
#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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
/* 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
|
||||||
|
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
|
guacenc_log(GUAC_LOG_ERROR, "Unable to write frame "
|
||||||
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
|
"#%" PRId64 ": %s", video->next_pts, strerror(errno));
|
||||||
return -1;
|
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",
|
guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes",
|
||||||
video->next_pts, size);
|
video->next_pts, size);
|
||||||
|
|
||||||
return 0;
|
return ret;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
||||||
@ -113,6 +145,7 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
|
|
||||||
/* For libavcodec < 57.37.100: input/output was not decoupled */
|
/* For libavcodec < 57.37.100: input/output was not decoupled */
|
||||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57,37,100)
|
||||||
|
|
||||||
/* Write frame to video */
|
/* Write frame to video */
|
||||||
int got_data;
|
int got_data;
|
||||||
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
|
if (avcodec_encode_video2(video->context, &packet, frame, &got_data) < 0) {
|
||||||
@ -123,10 +156,12 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
|
|
||||||
/* Write corresponding data to file */
|
/* Write corresponding data to file */
|
||||||
if (got_data) {
|
if (got_data) {
|
||||||
guacenc_write_packet(video, packet.data, packet.size);
|
guacenc_write_packet(video, (void*) &packet, packet.size);
|
||||||
av_packet_unref(&packet);
|
av_packet_unref(&packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/* Write frame to video */
|
/* Write frame to video */
|
||||||
int result = avcodec_send_frame(video->context, frame);
|
int result = avcodec_send_frame(video->context, frame);
|
||||||
|
|
||||||
@ -149,10 +184,11 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
got_data = 1;
|
got_data = 1;
|
||||||
|
|
||||||
/* Attempt to write data to output file */
|
/* 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);
|
av_packet_unref(&packet);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Frame may have been queued for later writing / reordering */
|
/* Frame may have been queued for later writing / reordering */
|
||||||
@ -165,3 +201,54 @@ int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) {
|
|||||||
#endif
|
#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->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 = 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)
|
||||||
|
return codecpar_ret;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -52,6 +52,16 @@
|
|||||||
#define av_packet_unref av_free_packet
|
#define av_packet_unref av_free_packet
|
||||||
#endif
|
#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_* */
|
/* For libavutil < 51.42.0: AV_PIX_FMT_* was PIX_FMT_* */
|
||||||
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
|
#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
|
||||||
#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
|
#define AV_PIX_FMT_RGB32 PIX_FMT_RGB32
|
||||||
@ -78,5 +88,78 @@
|
|||||||
*/
|
*/
|
||||||
int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame);
|
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.
|
||||||
|
*
|
||||||
|
* @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 height 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.
|
||||||
|
*
|
||||||
|
* @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,
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* Zero on success, a negative value on error.
|
||||||
|
*/
|
||||||
|
int guacenc_open_avcodec(AVCodecContext *avcodec_context,
|
||||||
|
AVCodec *codec, AVDictionary **options,
|
||||||
|
AVStream* stream);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "parse.h"
|
#include "parse.h"
|
||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
@ -80,6 +81,10 @@ int main(int argc, char* argv[]) {
|
|||||||
avcodec_register_all();
|
avcodec_register_all();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
||||||
|
av_register_all();
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Track number of overall failures */
|
/* Track number of overall failures */
|
||||||
int total_files = argc - optind;
|
int total_files = argc - optind;
|
||||||
int failures = 0;
|
int failures = 0;
|
||||||
|
@ -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
|
its input from files instead of a network connection, and renders directly to
|
||||||
video instead of to the user's screen.
|
video instead of to the user's screen.
|
||||||
.P
|
.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
|
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
|
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
|
with a bitrate of \fI2000000\fR bits per second (2 Mbps). These defaults can be
|
||||||
|
@ -25,6 +25,9 @@
|
|||||||
|
|
||||||
#include <cairo/cairo.h>
|
#include <cairo/cairo.h>
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
|
#ifndef AVFORMAT_AVFORMAT_H
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#endif
|
||||||
#include <libavutil/common.h>
|
#include <libavutil/common.h>
|
||||||
#include <libavutil/imgutils.h>
|
#include <libavutil/imgutils.h>
|
||||||
#include <libswscale/swscale.h>
|
#include <libswscale/swscale.h>
|
||||||
@ -43,6 +46,21 @@
|
|||||||
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
||||||
int width, int height, int bitrate) {
|
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 */
|
/* Pull codec based on name */
|
||||||
AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
|
AVCodec* codec = avcodec_find_encoder_by_name(codec_name);
|
||||||
if (codec == NULL) {
|
if (codec == NULL) {
|
||||||
@ -51,25 +69,35 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
|||||||
goto fail_codec;
|
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 */
|
/* Retrieve encoding context */
|
||||||
AVCodecContext* context = avcodec_alloc_context3(codec);
|
AVCodecContext* avcodec_context =
|
||||||
if (context == NULL) {
|
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 "
|
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for "
|
||||||
"codec \"%s\".", codec_name);
|
"codec \"%s\".", codec_name);
|
||||||
goto fail_context;
|
goto fail_context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init context with encoding parameters */
|
/* If format needs global headers, write them */
|
||||||
context->bit_rate = bitrate;
|
if (container_format_context->oformat->flags & AVFMT_GLOBALHEADER) {
|
||||||
context->width = width;
|
avcodec_context->flags |= GUACENC_FLAG_GLOBAL_HEADER;
|
||||||
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;
|
|
||||||
|
|
||||||
/* Open codec for use */
|
/* 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);
|
guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name);
|
||||||
goto fail_codec_open;
|
goto fail_codec_open;
|
||||||
}
|
}
|
||||||
@ -81,9 +109,9 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Copy necessary data for frame from context */
|
/* Copy necessary data for frame from context */
|
||||||
frame->format = context->pix_fmt;
|
frame->format = avcodec_context->pix_fmt;
|
||||||
frame->width = context->width;
|
frame->width = avcodec_context->width;
|
||||||
frame->height = context->height;
|
frame->height = avcodec_context->height;
|
||||||
|
|
||||||
/* Allocate actual backing data for frame */
|
/* Allocate actual backing data for frame */
|
||||||
if (av_image_alloc(frame->data, frame->linesize, frame->width,
|
if (av_image_alloc(frame->data, frame->linesize, frame->width,
|
||||||
@ -91,31 +119,32 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
|||||||
goto fail_frame_data;
|
goto fail_frame_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Open output file */
|
/* Open output file, if the container needs it */
|
||||||
int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
|
if (!(container_format->flags & AVFMT_NOFILE)) {
|
||||||
if (fd == -1) {
|
ret = avio_open(&container_format_context->pb, path, AVIO_FLAG_WRITE);
|
||||||
guacenc_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s",
|
if (ret < 0) {
|
||||||
path, strerror(errno));
|
guacenc_log(GUAC_LOG_ERROR, "Error occurred while opening output file.\n");
|
||||||
goto fail_output_fd;
|
goto fail_output_avio;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create stream for output file */
|
/* write the stream header, if needed */
|
||||||
FILE* output = fdopen(fd, "wb");
|
ret = avformat_write_header(container_format_context, NULL);
|
||||||
if (output == NULL) {
|
if (ret < 0) {
|
||||||
guacenc_log(GUAC_LOG_ERROR, "Failed to allocate stream for output "
|
guacenc_log(GUAC_LOG_ERROR, "Error occurred while writing output file header.\n");
|
||||||
"file \"%s\": %s", path, strerror(errno));
|
failed_header = true;
|
||||||
goto fail_output_file;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Allocate video structure */
|
/* Allocate video structure */
|
||||||
guacenc_video* video = malloc(sizeof(guacenc_video));
|
guacenc_video* video = malloc(sizeof(guacenc_video));
|
||||||
if (video == NULL) {
|
if (video == NULL) {
|
||||||
goto fail_video;
|
goto fail_output_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Init properties of video */
|
/* Init properties of video */
|
||||||
video->output = output;
|
video->output_stream = video_stream;
|
||||||
video->context = context;
|
video->context = avcodec_context;
|
||||||
|
video->container_format_context = container_format_context;
|
||||||
video->next_frame = frame;
|
video->next_frame = frame;
|
||||||
video->width = width;
|
video->width = width;
|
||||||
video->height = height;
|
video->height = height;
|
||||||
@ -125,16 +154,24 @@ guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name,
|
|||||||
video->last_timestamp = 0;
|
video->last_timestamp = 0;
|
||||||
video->next_pts = 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;
|
return video;
|
||||||
|
|
||||||
/* Free all allocated data in case of failure */
|
/* Free all allocated data in case of failure */
|
||||||
fail_video:
|
|
||||||
fclose(output);
|
|
||||||
|
|
||||||
fail_output_file:
|
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]);
|
av_freep(&frame->data[0]);
|
||||||
|
|
||||||
fail_frame_data:
|
fail_frame_data:
|
||||||
@ -142,7 +179,13 @@ fail_frame_data:
|
|||||||
|
|
||||||
fail_frame:
|
fail_frame:
|
||||||
fail_codec_open:
|
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_context:
|
||||||
fail_codec:
|
fail_codec:
|
||||||
@ -435,26 +478,34 @@ int guacenc_video_free(guacenc_video* video) {
|
|||||||
/* Write final frame */
|
/* Write final frame */
|
||||||
guacenc_video_flush_frame(video);
|
guacenc_video_flush_frame(video);
|
||||||
|
|
||||||
/* Init video packet for final flush of encoded data */
|
|
||||||
AVPacket packet;
|
|
||||||
av_init_packet(&packet);
|
|
||||||
|
|
||||||
/* Flush any unwritten frames */
|
/* Flush any unwritten frames */
|
||||||
int retval;
|
int retval;
|
||||||
do {
|
do {
|
||||||
retval = guacenc_video_write_frame(video, NULL);
|
retval = guacenc_video_write_frame(video, NULL);
|
||||||
} while (retval > 0);
|
} 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 */
|
/* 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 */
|
/* Free frame encoding data */
|
||||||
av_freep(&video->next_frame->data[0]);
|
av_freep(&video->next_frame->data[0]);
|
||||||
av_frame_free(&video->next_frame);
|
av_frame_free(&video->next_frame);
|
||||||
|
|
||||||
/* Clean up encoding context */
|
/* Clean up encoding context */
|
||||||
|
if (video->context != NULL) {
|
||||||
avcodec_close(video->context);
|
avcodec_close(video->context);
|
||||||
avcodec_free_context(&(video->context));
|
avcodec_free_context(&(video->context));
|
||||||
|
}
|
||||||
|
|
||||||
free(video);
|
free(video);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -26,6 +26,14 @@
|
|||||||
#include <guacamole/timestamp.h>
|
#include <guacamole/timestamp.h>
|
||||||
#include <libavcodec/avcodec.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 <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@ -42,9 +50,11 @@
|
|||||||
typedef struct guacenc_video {
|
typedef struct guacenc_video {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Output file stream.
|
* AVStream for video output.
|
||||||
|
* Frames sent to this stream are written into
|
||||||
|
* the output file in the specified container format.
|
||||||
*/
|
*/
|
||||||
FILE* output;
|
AVStream* output_stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The open encoding context from libavcodec, created for the codec
|
* The open encoding context from libavcodec, created for the codec
|
||||||
@ -52,6 +62,12 @@ typedef struct guacenc_video {
|
|||||||
*/
|
*/
|
||||||
AVCodecContext* context;
|
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.
|
* The width of the video, in pixels.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user