diff --git a/src/guacenc/Makefile.am b/src/guacenc/Makefile.am index 12309920..45b9f377 100644 --- a/src/guacenc/Makefile.am +++ b/src/guacenc/Makefile.am @@ -51,6 +51,7 @@ guacenc_SOURCES = \ display-layers.c \ display-sync.c \ encode.c \ + ffmpeg-compat.c \ guacenc.c \ image-stream.c \ instructions.c \ diff --git a/src/guacenc/ffmpeg-compat.c b/src/guacenc/ffmpeg-compat.c new file mode 100644 index 00000000..9cd68207 --- /dev/null +++ b/src/guacenc/ffmpeg-compat.c @@ -0,0 +1,123 @@ +/* + * 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 "ffmpeg-compat.h" +#include "log.h" +#include "video.h" + +#include +#include +#include +#include + +#include +#include +#include + +int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame) { + +/* For libavcodec < 54.1.0: avcodec_encode_video2() did not exist */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,1,0) + + AVCodecContext* context = video->context; + + /* Calculate appropriate buffer size */ + int length = FF_MIN_BUFFER_SIZE + 12 * context->width * context->height; + + /* Allocate space for output */ + uint8_t* data = malloc(length); + if (data == NULL) + return -1; + + /* Encode packet of video data */ + int used = avcodec_encode_video(context, data, length, frame); + if (used < 0) { + guacenc_log(GUAC_LOG_WARNING, "Error encoding frame #%" PRId64, + video->next_pts); + free(data); + return -1; + } + + /* Report if no data needs to be written */ + if (used == 0) { + free(data); + return 0; + } + + /* Write data, logging any errors */ + if (fwrite(data, 1, used, video->output) == 0) { + guacenc_log(GUAC_LOG_ERROR, "Unable to write frame " + "#%" PRId64 ": %s", video->next_pts, strerror(errno)); + free(data); + return -1; + } + + /* Data was written successfully */ + free(data); + return 1; + +#else + + /* Init video packet */ + AVPacket packet; + av_init_packet(&packet); + + /* Request that encoder allocate data for packet */ + packet.data = NULL; + packet.size = 0; + + /* 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) { + + /* Write data, logging any errors */ + if (fwrite(packet.data, 1, packet.size, video->output) == 0) { + guacenc_log(GUAC_LOG_ERROR, "Unable to write frame " + "#%" PRId64 ": %s", video->next_pts, strerror(errno)); + return -1; + } + + /* Data was written successfully */ + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", + video->next_pts, packet.size); + av_packet_unref(&packet); + + } + + /* Frame may have been queued for later writing / reordering */ + else + guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": queued for later", + video->next_pts); + + return got_data; + +#endif +} + diff --git a/src/guacenc/ffmpeg-compat.h b/src/guacenc/ffmpeg-compat.h index ba3213a5..3e5255cd 100644 --- a/src/guacenc/ffmpeg-compat.h +++ b/src/guacenc/ffmpeg-compat.h @@ -24,6 +24,7 @@ #define GUACENC_FFMPEG_COMPAT_H #include "config.h" +#include "video.h" #include @@ -39,6 +40,11 @@ #define av_frame_free avcodec_free_frame #endif +/* For libavcodec < 54.28.0: old avcodec_free_frame() did not exist. */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54,28,0) +#define avcodec_free_frame av_freep +#endif + /* For libavcodec < 55.52.0: avcodec_free_context did not exist */ #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,52,0) #define avcodec_free_context av_freep @@ -49,5 +55,31 @@ #define av_packet_unref av_free_packet #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 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + +/** + * Writes the specied frame as a new frame of video. If pending frames of the + * video are being flushed, the given frame may be NULL (as required by + * avcodec_encode_video2()). If avcodec_encode_video2() does not exist, this + * function will transparently use avcodec_encode_video(). + * + * @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. + */ +int guacenc_avcodec_encode_video(guacenc_video* video, AVFrame* frame); + #endif diff --git a/src/guacenc/video.c b/src/guacenc/video.c index 8c138442..60a9477e 100644 --- a/src/guacenc/video.c +++ b/src/guacenc/video.c @@ -174,47 +174,14 @@ fail_codec: */ 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); + int got_data = guacenc_avcodec_encode_video(video, frame); + if (got_data < 0) return -1; - } - - /* Write corresponding data to file */ - if (got_data) { - - /* Write data, logging any errors */ - if (fwrite(packet.data, 1, packet.size, video->output) == 0) { - guacenc_log(GUAC_LOG_ERROR, "Unable to write frame " - "#%" PRId64 ": %s", video->next_pts, strerror(errno)); - return -1; - } - - /* Data was written successfully */ - guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": wrote %i bytes", - video->next_pts, packet.size); - av_packet_unref(&packet); - - } - - /* Frame may have been queued for later writing / reordering */ - else - guacenc_log(GUAC_LOG_DEBUG, "Frame #%08" PRId64 ": queued for later", - video->next_pts); /* Update presentation timestamp for next frame */ video->next_pts++;