video.c (15853B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20#include "config.h" 21#include "buffer.h" 22#include "ffmpeg-compat.h" 23#include "log.h" 24#include "video.h" 25 26#include <cairo/cairo.h> 27#include <libavcodec/avcodec.h> 28#ifndef AVFORMAT_AVFORMAT_H 29#include <libavformat/avformat.h> 30#endif 31#include <libavutil/common.h> 32#include <libavutil/imgutils.h> 33#include <libswscale/swscale.h> 34#include <guacamole/client.h> 35#include <guacamole/mem.h> 36#include <guacamole/timestamp.h> 37 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <assert.h> 41#include <errno.h> 42#include <fcntl.h> 43#include <inttypes.h> 44#include <stdlib.h> 45#include <string.h> 46#include <unistd.h> 47 48guacenc_video* guacenc_video_alloc(const char* path, const char* codec_name, 49 int width, int height, int bitrate) { 50 51 AVOutputFormat *container_format; 52 AVFormatContext *container_format_context; 53 AVStream *video_stream; 54 int ret; 55 int failed_header = 0; 56 57 /* allocate the output media context */ 58 avformat_alloc_output_context2(&container_format_context, NULL, NULL, path); 59 if (container_format_context == NULL) { 60 guacenc_log(GUAC_LOG_ERROR, "Failed to determine container from output file name"); 61 goto fail_codec; 62 } 63 64 container_format = (AVOutputFormat *) container_format_context->oformat; 65 66 /* Pull codec based on name */ 67 AVCodec* codec = (AVCodec *)avcodec_find_encoder_by_name(codec_name); 68 if (codec == NULL) { 69 guacenc_log(GUAC_LOG_ERROR, "Failed to locate codec \"%s\".", 70 codec_name); 71 goto fail_codec; 72 } 73 74 /* create stream */ 75 video_stream = NULL; 76 video_stream = avformat_new_stream(container_format_context, codec); 77 if (video_stream == NULL) { 78 guacenc_log(GUAC_LOG_ERROR, "Could not allocate encoder stream. Cannot continue."); 79 goto fail_format_context; 80 } 81 video_stream->id = container_format_context->nb_streams - 1; 82 83 /* Retrieve encoding context */ 84 AVCodecContext* avcodec_context = 85 guacenc_build_avcodeccontext(video_stream, codec, bitrate, width, 86 height, /*gop size*/ 10, /*qmax*/ 31, /*qmin*/ 2, 87 /*pix fmt*/ AV_PIX_FMT_YUV420P, 88 /*time base*/ (AVRational) { 1, GUACENC_VIDEO_FRAMERATE }); 89 90 if (avcodec_context == NULL) { 91 guacenc_log(GUAC_LOG_ERROR, "Failed to allocate context for " 92 "codec \"%s\".", codec_name); 93 goto fail_context; 94 } 95 96 /* If format needs global headers, write them */ 97 if (container_format_context->oformat->flags & AVFMT_GLOBALHEADER) { 98 avcodec_context->flags |= GUACENC_FLAG_GLOBAL_HEADER; 99 } 100 101 /* Open codec for use */ 102 if (guacenc_open_avcodec(avcodec_context, codec, NULL, video_stream) < 0) { 103 guacenc_log(GUAC_LOG_ERROR, "Failed to open codec \"%s\".", codec_name); 104 goto fail_codec_open; 105 } 106 107 /* Allocate corresponding frame */ 108 AVFrame* frame = av_frame_alloc(); 109 if (frame == NULL) { 110 goto fail_frame; 111 } 112 113 /* Copy necessary data for frame from context */ 114 frame->format = avcodec_context->pix_fmt; 115 frame->width = avcodec_context->width; 116 frame->height = avcodec_context->height; 117 118 /* Allocate actual backing data for frame */ 119 if (av_image_alloc(frame->data, frame->linesize, frame->width, 120 frame->height, frame->format, 32) < 0) { 121 goto fail_frame_data; 122 } 123 124 /* Open output file, if the container needs it */ 125 if (!(container_format->flags & AVFMT_NOFILE)) { 126 ret = avio_open(&container_format_context->pb, path, AVIO_FLAG_WRITE); 127 if (ret < 0) { 128 guacenc_log(GUAC_LOG_ERROR, "Error occurred while opening output file."); 129 goto fail_output_avio; 130 } 131 } 132 133 /* write the stream header, if needed */ 134 ret = avformat_write_header(container_format_context, NULL); 135 if (ret < 0) { 136 guacenc_log(GUAC_LOG_ERROR, "Error occurred while writing output file header."); 137 failed_header = true; 138 goto fail_output_file; 139 } 140 141 /* Allocate video structure */ 142 guacenc_video* video = guac_mem_alloc(sizeof(guacenc_video)); 143 if (video == NULL) 144 goto fail_alloc_video; 145 146 /* Init properties of video */ 147 video->output_stream = video_stream; 148 video->context = avcodec_context; 149 video->container_format_context = container_format_context; 150 video->next_frame = frame; 151 video->width = width; 152 video->height = height; 153 video->bitrate = bitrate; 154 155 /* No frames have been written or prepared yet */ 156 video->last_timestamp = 0; 157 video->next_pts = 0; 158 159 return video; 160 161 /* Free all allocated data in case of failure */ 162fail_alloc_video: 163fail_output_file: 164 avio_close(container_format_context->pb); 165 166 /* Delete the file that was created if it was actually created */ 167 if (unlink(path) == -1 && errno != ENOENT) 168 guacenc_log(GUAC_LOG_WARNING, "Failed output file \"%s\" could not " 169 "be automatically deleted: %s", path, strerror(errno)); 170 171fail_output_avio: 172 av_freep(&frame->data[0]); 173 174fail_frame_data: 175 av_frame_free(&frame); 176 177fail_frame: 178fail_codec_open: 179 avcodec_free_context(&avcodec_context); 180 181fail_format_context: 182 /* failing to write the container implicitly frees the context */ 183 if (!failed_header) { 184 avformat_free_context(container_format_context); 185 } 186 187fail_context: 188fail_codec: 189 return NULL; 190 191} 192 193/** 194 * Flushes the specied frame as a new frame of video, updating the internal 195 * video timestamp by one frame's worth of time. The pts member of the given 196 * frame structure will be updated with the current presentation timestamp of 197 * the video. If pending frames of the video are being flushed, the given frame 198 * may be NULL (as required by avcodec_encode_video2()). 199 * 200 * @param video 201 * The video to write the given frame to. 202 * 203 * @param frame 204 * The frame to write to the video, or NULL if previously-written frames 205 * are being flushed. 206 * 207 * @return 208 * A positive value if the frame was successfully written, zero if the 209 * frame has been saved for later writing / reordering, negative if an 210 * error occurs. 211 */ 212static int guacenc_video_write_frame(guacenc_video* video, AVFrame* frame) { 213 214 /* Set timestamp of frame, if frame given */ 215 if (frame != NULL) 216 frame->pts = video->next_pts; 217 218 /* Write frame to video */ 219 int got_data = guacenc_avcodec_encode_video(video, frame); 220 if (got_data < 0) 221 return -1; 222 223 /* Update presentation timestamp for next frame */ 224 video->next_pts++; 225 226 /* Write was successful */ 227 return got_data; 228 229} 230 231/** 232 * Flushes the frame previously specified by guacenc_video_prepare_frame() as a 233 * new frame of video, updating the internal video timestamp by one frame's 234 * worth of time. 235 * 236 * @param video 237 * The video to flush. 238 * 239 * @return 240 * Zero if flushing was successful, non-zero if an error occurs. 241 */ 242static int guacenc_video_flush_frame(guacenc_video* video) { 243 244 /* Write frame to video */ 245 return guacenc_video_write_frame(video, video->next_frame) < 0; 246 247} 248 249int guacenc_video_advance_timeline(guacenc_video* video, 250 guac_timestamp timestamp) { 251 252 guac_timestamp next_timestamp = timestamp; 253 254 /* Flush frames as necessary if previously updated */ 255 if (video->last_timestamp != 0) { 256 257 /* Calculate the number of frames that should have been written */ 258 int elapsed = (timestamp - video->last_timestamp) 259 * GUACENC_VIDEO_FRAMERATE / 1000; 260 261 /* Keep previous timestamp if insufficient time has elapsed */ 262 if (elapsed == 0) 263 return 0; 264 265 /* Use frame time as last_timestamp */ 266 next_timestamp = video->last_timestamp 267 + elapsed * 1000 / GUACENC_VIDEO_FRAMERATE; 268 269 /* Flush frames to bring timeline in sync, duplicating if necessary */ 270 do { 271 if (guacenc_video_flush_frame(video)) { 272 guacenc_log(GUAC_LOG_ERROR, "Unable to flush frame to video " 273 "stream."); 274 return 1; 275 } 276 } while (--elapsed != 0); 277 278 } 279 280 /* Update timestamp */ 281 video->last_timestamp = next_timestamp; 282 return 0; 283 284} 285 286/** 287 * Converts the given Guacamole video encoder buffer to a frame in the format 288 * required by libavcodec / libswscale. Black margins of the specified sizes 289 * will be added. No scaling is performed; the image data is copied verbatim. 290 * 291 * @param buffer 292 * The guacenc_buffer to copy as a new AVFrame. 293 * 294 * @param lsize 295 * The size of the letterboxes to add, in pixels. Letterboxes are the 296 * horizontal black boxes added to images which are scaled down to fit the 297 * destination because they are too wide (the width is scaled to exactly 298 * fit the destination, resulting in extra space at the top and bottom). 299 * 300 * @param psize 301 * The size of the pillarboxes to add, in pixels. Pillarboxes are the 302 * vertical black boxes added to images which are scaled down to fit the 303 * destination because they are too tall (the height is scaled to exactly 304 * fit the destination, resulting in extra space on the sides). 305 * 306 * @return 307 * A pointer to a newly-allocated AVFrame containing exactly the same image 308 * data as the given buffer. The image data within the frame and the frame 309 * itself must be manually freed later. 310 */ 311static AVFrame* guacenc_video_frame_convert(guacenc_buffer* buffer, int lsize, 312 int psize) { 313 314 /* Init size of left/right pillarboxes */ 315 int left = psize; 316 int right = psize; 317 318 /* Init size of top/bottom letterboxes */ 319 int top = lsize; 320 int bottom = lsize; 321 322 /* Prepare source frame for buffer */ 323 AVFrame* frame = av_frame_alloc(); 324 if (frame == NULL) 325 return NULL; 326 327 /* Copy buffer properties to frame */ 328 frame->format = AV_PIX_FMT_RGB32; 329 frame->width = buffer->width + left + right; 330 frame->height = buffer->height + top + bottom; 331 332 /* Allocate actual backing data for frame */ 333 if (av_image_alloc(frame->data, frame->linesize, frame->width, 334 frame->height, frame->format, 32) < 0) { 335 av_frame_free(&frame); 336 return NULL; 337 } 338 339 /* Flush any pending operations */ 340 cairo_surface_flush(buffer->surface); 341 342 /* Get pointer to source image data */ 343 unsigned char* src_data = buffer->image; 344 int src_stride = buffer->stride; 345 346 /* Get pointer to destination image data */ 347 unsigned char* dst_data = frame->data[0]; 348 int dst_stride = frame->linesize[0]; 349 350 /* Get source/destination dimensions */ 351 int width = buffer->width; 352 int height = buffer->height; 353 354 /* Source buffer is guaranteed to fit within destination buffer */ 355 assert(width <= frame->width); 356 assert(height <= frame->height); 357 358 /* Add top margin */ 359 while (top > 0) { 360 memset(dst_data, 0, frame->width * 4); 361 dst_data += dst_stride; 362 top--; 363 } 364 365 /* Copy all data from source buffer to destination frame */ 366 while (height > 0) { 367 368 /* Calculate size of margin and data regions */ 369 int left_size = left * 4; 370 int data_size = width * 4; 371 int right_size = right * 4; 372 373 /* Add left margin */ 374 memset(dst_data, 0, left_size); 375 376 /* Copy data */ 377 memcpy(dst_data + left_size, src_data, data_size); 378 379 /* Add right margin */ 380 memset(dst_data + left_size + data_size, 0, right_size); 381 382 dst_data += dst_stride; 383 src_data += src_stride; 384 385 height--; 386 387 } 388 389 /* Add bottom margin */ 390 while (bottom > 0) { 391 memset(dst_data, 0, frame->width * 4); 392 dst_data += dst_stride; 393 bottom--; 394 } 395 396 /* Frame converted */ 397 return frame; 398 399} 400 401void guacenc_video_prepare_frame(guacenc_video* video, guacenc_buffer* buffer) { 402 403 int lsize; 404 int psize; 405 406 /* Ignore NULL buffers */ 407 if (buffer == NULL || buffer->surface == NULL) 408 return; 409 410 /* Obtain destination frame */ 411 AVFrame* dst = video->next_frame; 412 413 /* Determine width of image if height is scaled to match destination */ 414 int scaled_width = buffer->width * dst->height / buffer->height; 415 416 /* Determine height of image if width is scaled to match destination */ 417 int scaled_height = buffer->height * dst->width / buffer->width; 418 419 /* If height-based scaling results in a fit width, add pillarboxes */ 420 if (scaled_width <= dst->width) { 421 lsize = 0; 422 psize = (dst->width - scaled_width) 423 * buffer->height / dst->height / 2; 424 } 425 426 /* If width-based scaling results in a fit width, add letterboxes */ 427 else { 428 assert(scaled_height <= dst->height); 429 psize = 0; 430 lsize = (dst->height - scaled_height) 431 * buffer->width / dst->width / 2; 432 } 433 434 /* Prepare source frame for buffer */ 435 AVFrame* src = guacenc_video_frame_convert(buffer, lsize, psize); 436 if (src == NULL) { 437 guacenc_log(GUAC_LOG_WARNING, "Failed to allocate source frame. " 438 "Frame dropped."); 439 return; 440 } 441 442 /* Prepare scaling context */ 443 struct SwsContext* sws = sws_getContext(src->width, src->height, 444 AV_PIX_FMT_RGB32, dst->width, dst->height, AV_PIX_FMT_YUV420P, 445 SWS_BICUBIC, NULL, NULL, NULL); 446 447 /* Abort if scaling context could not be created */ 448 if (sws == NULL) { 449 guacenc_log(GUAC_LOG_WARNING, "Failed to allocate software scaling " 450 "context. Frame dropped."); 451 av_freep(&src->data[0]); 452 av_frame_free(&src); 453 return; 454 } 455 456 /* Apply scaling, copying the source frame to the destination */ 457 sws_scale(sws, (const uint8_t* const*) src->data, src->linesize, 458 0, src->height, dst->data, dst->linesize); 459 460 /* Free scaling context */ 461 sws_freeContext(sws); 462 463 /* Free source frame */ 464 av_freep(&src->data[0]); 465 av_frame_free(&src); 466 467} 468 469int guacenc_video_free(guacenc_video* video) { 470 471 /* Ignore NULL video */ 472 if (video == NULL) 473 return 0; 474 475 /* Write final frame */ 476 guacenc_video_flush_frame(video); 477 478 /* Flush any unwritten frames */ 479 int retval; 480 do { 481 retval = guacenc_video_write_frame(video, NULL); 482 } while (retval > 0); 483 484 /* write trailer, if needed */ 485 if (video->container_format_context != NULL && 486 video->output_stream != NULL) { 487 guacenc_log(GUAC_LOG_DEBUG, "Writing trailer: %s", 488 av_write_trailer(video->container_format_context) == 0 ? 489 "success" : "failure"); 490 } 491 492 /* File is now completely written */ 493 if (video->container_format_context != NULL) { 494 avio_close(video->container_format_context->pb); 495 } 496 497 /* Free frame encoding data */ 498 av_freep(&video->next_frame->data[0]); 499 av_frame_free(&video->next_frame); 500 501 /* Clean up encoding context */ 502 if (video->context != NULL) { 503 avcodec_close(video->context); 504 avcodec_free_context(&(video->context)); 505 } 506 507 guac_mem_free(video); 508 return 0; 509 510} 511