[Ffmpeg-devel] logo.so module
jserv at linux2.cc.ntu.edu.tw
jserv
Sat Oct 14 20:27:55 CEST 2006
On Sat, Oct 14, 2006 at 08:07:23PM +0200, Diego Biurrun wrote:
> I recently cleaned up all license headers in FFmpeg, please fix this one
> using the others as a template. "This library" should be FFmpeg, LGPL
> version should be 2.1.
hi Diego,
Thanks for noticing. I have attached the new patch in this mail.
Sincerely,
Jim Huang
-------------- next part --------------
Index: vhook/logo.c
===================================================================
--- vhook/logo.c (revision 0)
+++ vhook/logo.c (revision 0)
@@ -0,0 +1,672 @@
+/**
+ * Composite an alpha-channel logo with optional drop-shadow onto video.
+ *
+ * Copyright (c) 2006 Bob (grafman) Free
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * \file logo.c
+ * Composite an alpha-channel logo with optional drop-shadow onto video.
+ *
+ * Sponsored by Fabrik Inc. - bfree(at)fabrikinc.com
+ * \author Bob (grafman) Free - bfree(at)graphcomp.com
+ *
+ * This vhook demonstrates the use of av_read_image to load still
+ * images with alpha/transparency and composite them on video.
+ *
+ * Note: PNG support requires that FFMPEG be built with the PNG
+ * codec registered/enabled.
+ *
+ ******************************************************************************
+ * EXAMPLE USAGE:
+ *
+ * ffmpeg -i INFILE -vhook 'PATH/logo.so -f logo.gif' OUTFILE
+ *
+ *
+ * Note: the entire vhook argument must be single-quoted.
+ *
+ *
+ * REQUIRED ARGS:
+ *
+ * -f <FILEPATH>
+ *
+ * Specifies the image to use for the logo. GIF is supported
+ * in normal FFMPEG builds; PNG is supported if enabled in libavcodec
+ * and libavformat.
+ *
+ *
+ * OPTIONAL ARGS:
+ *
+ * -x <INT>
+ *
+ * Defines a logo offset from the left side of the frame.
+ * A negative value (including -0) offsets from the right side.
+ *
+ * -y <INT>
+ *
+ * Defines a logo offset from the top of the frame.
+ * A negative value (including -0) offsets from the bottom.
+ *
+ * -w <INT>
+ *
+ * Defines a drop shadow to the right of the logo.
+ * A negative value shifts the shadow to the left.
+ *
+ * -h <INT>
+ *
+ * Defines a drop shadow to the bottom of the logo.
+ * A negative value shifts the shadow upward.
+ *
+ * -d <INT>
+ *
+ * Defines the percent opacity of the drop shadow (0 - 100);
+ * 100 is opaque. Defaults to 75.
+ *
+ *
+ * Sample logos and additional notes available at
+ * http://graphcomp.com/ffmpeg#plugins
+ *
+ * Modified by Jim Huang <jserv.tw at gmail.com>
+ * - Remove SegFault when image type is not supported. (JPEG detection).
+ * - Eliminate compilation warnings.
+ * - Code cleanup.
+ */
+
+/* Includes */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "common.h"
+#include "avformat.h"
+#include "avcodec.h"
+#include "allformats.h"
+
+#include "framehook.h"
+#include "cmdutils.h"
+
+
+/* This stuff belongs in a header file */
+
+/**
+ * RGBA pixel.
+ */
+typedef struct
+{
+ uint8_t R; ///< Red.
+ uint8_t G; ///< Green.
+ uint8_t B; ///< Blue.
+ uint8_t A; ///< Alpha.
+} RGBA;
+
+/**
+ * RECT - bounding rectangle.
+ */
+typedef struct
+{
+ int left;
+ int top;
+ int right;
+ int bottom;
+} RECT;
+
+/*
+ * Constants.
+ */
+#define MAX_FILEPATH 2048 ///< Max filepath length.
+
+/**
+ * ContextInfo - shares data between vhook functions.
+ */
+typedef struct
+{
+ // User parameters - populated by Configure.
+ char filename[MAX_FILEPATH]; ///< Logo filepath.
+ int xDir; ///< Horizontal offset direction.
+ int yDir; ///< Vertical offset direction.
+ int xOff; ///< Horizontal offset in pixels.
+ int yOff; ///< Vertical offset in pixels.
+ int xDrop; ///< Horizontal drop-shadow offset.
+ int yDrop; ///< Vertical drop-shadow offset.
+ int dOpacity; ///< Drop-shadow opacity (0-100).
+
+ // Image cache buffers - populated by load_image.
+ int image_loaded; ///< Image cache loaded flag.
+ int width; ///< Logo width in pixels.
+ int height; ///< Logo height in pixels.
+ int inp_fmt; ///< Logo format constant.
+ int inp_size; ///< Logo buffer size in bytes.
+ uint8_t* inp_buf; ///< Pointer to logo buffer.
+ int rgba_size; ///< RGBA buffer size in bytes.
+ uint8_t* rgba_buf; ///< Pointer to RGBA buffer.
+ AVFrame* rgbaFrame; ///< Pointer to RGBA frame.
+
+ // Bounds data - populated by calc_bounds.
+ int vidWidth; ///< Video frame width in pixels.
+ int vidHeight; ///< Video frame height in pixels.
+ int vid_row; ///< Video row size in bytes.
+ int logo_row; ///< Logo row size in bytes.
+ RECT rLogo; ///< Logo placement on video frame.
+ RECT rDrop; ///< Drop-shadow placement on video frame.
+ RECT rBounds; ///< Affected pixels on video frame.
+} ContextInfo;
+
+/*
+ * Macros.
+ */
+#ifndef min
+#define min(a,b) ((a < b) ? a : b) ///< Return the smaller of 2 values.
+#define max(a,b) ((a > b) ? a : b) ///< Return the larger of 2 values.
+#endif
+
+/**
+ * Load and cache an image.
+ * @param ci Context pointer.
+ * @return 0 for success; otherwise failure.
+ */
+static int load_image(ContextInfo *ci);
+
+/**
+ * Release an image cache.
+ * @param ci Context pointer.
+ */
+static void release_image(ContextInfo *ci);
+
+/**
+ * Initialization callback for av_read_image.
+ * @param opaque Context pointer.
+ * @param info Image info from codec.
+ * @return 0 for success; otherwise failure.
+ */
+static int read_image_alloc_cb(void *opaque, AVImageInfo *info);
+
+/**
+ * Calculates bounding info for video frame, logo and drop-shadow.
+ * @param ci Context pointer.
+ * @param vid_width Video frame width in pixels.
+ * @param vid_height Video frame height in pixels.
+ */
+static void calc_bounds(ContextInfo *ci, int vid_width, int vid_height);
+
+/**
+ * Merges a pixel component onto another using and alpha-channel value.
+ * @param back Background pixel value.
+ * @param fore Foreground pixel value.
+ * @param alpha Alpha-channel value - from 0.0 (transparent) to 1.0 (opaque).
+ * @return Merged component value.
+ */
+static int alpha_merge(int back, int fore, double alpha);
+
+
+/* Finally - the code */
+
+/**
+ * Allocate context block and capture user parameters.
+ * Called by FFMPEG pipeline.
+ * @param ctxp A handle to receive alloacted context pointer.
+ * @param argc vhook's argument count.
+ * @param argv vhook's argument pointers.
+ * @return 0 for success; otherwise failure.
+ */
+int Configure(void **ctxp, int argc, char *argv[])
+{
+ ContextInfo *ci;
+ int c;
+
+ // Allocate context block
+ if (0 == (*ctxp = av_mallocz(sizeof(ContextInfo)))) return -1;
+ ci = (ContextInfo *)*ctxp;
+
+ // Set drop shadow default to 75%
+ ci->dOpacity = 75;
+
+ // Parse user parameters
+ opterr = 0;
+ optind = 1;
+ while ((c = getopt(argc, argv, "f:x::y::w::h::d::")) > 0)
+ {
+ switch (c)
+ {
+ // Logo filepath
+ case 'f':
+ {
+ strncpy(ci->filename, optarg, MAX_FILEPATH-1);
+ ci->filename[MAX_FILEPATH-1] = 0;
+ break;
+ }
+ // Logo offset
+ case 'x':
+ {
+ ci->xDir = strchr(argv[optind],'-') ? -1 : 1;
+ ci->xOff = abs(atoi(argv[optind]));
+ break;
+ }
+ case 'y':
+ {
+ ci->yDir = strchr(argv[optind],'-') ? -1 : 1;
+ ci->yOff = abs(atoi(argv[optind]));
+ break;
+ }
+ // Drop shadow offset
+ case 'w':
+ {
+ ci->xDrop = atoi(argv[optind]);
+ break;
+ }
+ case 'h':
+ {
+ ci->yDrop = atoi(argv[optind]);
+ break;
+ }
+ // Drop shadow opacity
+ case 'd':
+ {
+ ci->dOpacity = atoi(argv[optind]);
+ if (ci->dOpacity < 0) ci->dOpacity = 0;
+ if (ci->dOpacity > 100) ci->dOpacity = 100;
+ break;
+ }
+ // Ignore unsupported args
+ default:
+ {
+ av_log(NULL, AV_LOG_DEBUG,
+ "logo: Unrecognized argument '-%c %s' - ignored\n",
+ c,argv[optind]);
+ }
+ }
+ }
+
+ // Check that a filepath was provided
+ if (0 == ci->filename[0])
+ {
+ av_log(NULL, AV_LOG_ERROR, "logo: No filepath specified.\n");
+ return -1;
+ }
+
+ // Register codecs
+ av_register_all();
+
+ // Load and cache logo
+ return(load_image(ci));
+}
+
+
+/**
+ * Release context block.
+ * Called by FFMPEG pipeline.
+ * @param ctx Context pointer.
+ */
+void Release(void *ctx)
+{
+ ContextInfo *ci = (ContextInfo *)ctx;
+
+ if (ci) release_image(ci);
+ if (ctx) av_free(ctx);
+}
+
+
+/**
+ * Main video frame proc.
+ * Called by FFMPEG pipeline.
+ * @param ctx Context pointer.
+ * @param picture Pointer to video frame.
+ * @param pix_fmt Video frame format constant.
+ * @param src_width Video frame width in pixels.
+ * @param src_heigth Video frame height in pixels.
+ * @param pts Presentation timestamp.
+ */
+void Process(void *ctx,
+ AVPicture *picture,
+ enum PixelFormat pix_fmt,
+ int src_width,
+ int src_height,
+ int64_t pts)
+{
+ ContextInfo *ci;
+ uint8_t *buf = 0;
+ AVPicture *pict = picture;
+ AVPicture rgbaPict;
+ int x, y, xLogo, yLogo, xDrop, yDrop;
+ int vid_offs, logo_offs, drop_offs;
+ RGBA *pVid;
+ RGBA *pLogo;
+ RGBA *pDrop;
+ double alpha;
+ double opacity;
+
+ // Skip if no frame dimensions
+ if (!src_width || !src_height) return;
+
+ // Initialize context
+ ci = (ContextInfo *) ctx;
+ calc_bounds(ci, src_width, src_height);
+
+ // Convert video frame to RGBA32 (easier to process for palette-based videos)
+ // Could optimize for non-palette based videos
+ if (pix_fmt != PIX_FMT_RGBA32)
+ {
+ int size = avpicture_get_size(PIX_FMT_RGBA32, src_width, src_height);
+ buf = av_malloc(size);
+
+ avpicture_fill(&rgbaPict, buf, PIX_FMT_RGBA32, src_width, src_height);
+ if (img_convert(&rgbaPict, PIX_FMT_RGBA32,
+ picture, pix_fmt, src_width, src_height) < 0)
+ {
+ av_free(buf);
+ return;
+ }
+ pict = &rgbaPict;
+ }
+
+ /* Insert filter code here, if any */
+
+ // Frame's row loop
+ for (y=ci->rBounds.top; y<ci->rBounds.bottom; y++)
+ {
+ // Get video frame pointer offset
+ vid_offs = y * ci->vid_row;
+
+ // Get logo pointer offset
+ yLogo = y - ci->rLogo.top;
+ logo_offs = yLogo * ci->logo_row;
+
+ // Get drop-shadow pointer offset
+ yDrop = y - ci->rDrop.top;
+ drop_offs = yDrop * ci->logo_row;
+
+ // Row's pixel loop
+ for (x=ci->rBounds.left; x<ci->rBounds.right; x++)
+ {
+ // Get pointer to video frame pixel
+ pVid = (RGBA *)(pict->data[0]+vid_offs+(x<<2));
+
+ // Handle drop-shadow first - skip if no drop-shadow offsets
+ if (ci->xDrop || ci->yDrop)
+ {
+ xDrop = x - ci->rDrop.left;
+
+ if (xDrop > 0 && xDrop < ci->width &&
+ yDrop > 0 && yDrop < ci->height)
+ {
+ // Get pointer to drop-shadow pixel
+ pDrop = (RGBA *)(ci->rgbaFrame->data[0]+drop_offs+(xDrop<<2));
+
+ // Lame drop shadow - gaussian distribution would be much better
+ opacity = ci->dOpacity * pDrop->A / 25500.0;
+
+ // Composite drop-shadow
+ if (opacity != 0.0)
+ {
+ pVid->R = alpha_merge(pVid->R,0,opacity);
+ pVid->G = alpha_merge(pVid->G,0,opacity);
+ pVid->B = alpha_merge(pVid->B,0,opacity);
+ }
+ }
+ }
+
+ // Handle logo next
+ xLogo = x - ci->rLogo.left;
+ if (yLogo > 0 && yLogo < ci->height &&
+ xLogo > 0 && xLogo < ci->width)
+ {
+ // Get pointer to logo pixel
+ pLogo = (RGBA *)(ci->rgbaFrame->data[0]+logo_offs+(xLogo<<2));
+
+ // If opaque, just copy
+ if (pLogo->A == 255)
+ {
+ *pVid = *pLogo;
+ }
+ // Skip if transparent - otherwise merge
+ else if (pLogo->A)
+ {
+ alpha = pLogo->A / 255.0;
+ pVid->R = alpha_merge(pVid->R,pLogo->R,alpha);
+ pVid->G = alpha_merge(pVid->G,pLogo->G,alpha);
+ pVid->B = alpha_merge(pVid->B,pLogo->B,alpha);
+ }
+ }
+ } // foreach X
+ } // foreach Y
+
+ // Convert modified frame back to video format
+ if (pix_fmt != PIX_FMT_RGBA32)
+ {
+ if (img_convert(picture, pix_fmt,
+ &rgbaPict, PIX_FMT_RGBA32, src_width, src_height) < 0)
+ {
+ // Error handling here
+ }
+ av_free(buf);
+ buf = 0;
+ }
+}
+
+
+/****************************************************************************
+ * Load and cache image buffers.
+ ****************************************************************************/
+static int load_image(ContextInfo *ci)
+{
+ AVImageFormat *pFormat;
+ ByteIOContext bctx,*pb=&bctx;
+ AVFrame *pFrameInp;
+ int err;
+
+ // Just return if logo has already been fetched/converted
+ if (ci->image_loaded) return 0;
+
+ // Guess image format
+ pFormat = guess_image_format(ci->filename);
+
+ // Unable to guess format
+ if (!pFormat)
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: Unsupported image format\n");
+ return -1;
+ }
+
+ // JPEG image decoder is broken - bail
+#if !defined(JPEG_FIXED)
+ if (!strcmp(pFormat->name,"jpeg"))
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: JPEG image format not supported\n");
+ return -1;
+ }
+#endif
+
+ // Open file
+ if (url_fopen(pb, ci->filename, URL_RDONLY) < 0)
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: Unable to open image\n");
+ return -1;
+ }
+
+ // Read file
+ err = av_read_image(pb, ci->filename, pFormat, read_image_alloc_cb, ci);
+ url_fclose(pb);
+
+ // Handle errors
+ if (!ci->inp_buf)
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: Unable to allocate read buffer\n");
+ return -1;
+ }
+ if (!ci->rgba_buf || !ci->rgbaFrame)
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: Unable to allocate rgba buffer\n");
+ return -1;
+ }
+ if (err)
+ {
+ av_log(NULL, AV_LOG_ERROR,"logo: av_read_image error: %d\n",err);
+ return -1;
+ }
+
+ // Convert to RGBA32 if necessary
+ if (ci->inp_fmt != PIX_FMT_RGBA32)
+ {
+ // Allocate input frame
+ pFrameInp = avcodec_alloc_frame();
+ avpicture_fill((AVPicture*)pFrameInp,
+ ci->inp_buf,ci->inp_fmt,ci->width,ci->height);
+
+ img_convert((AVPicture*)ci->rgbaFrame, PIX_FMT_RGBA32,
+ (AVPicture*)pFrameInp, ci->inp_fmt, ci->width, ci->height);
+
+ av_free(pFrameInp);
+ }
+
+ // Done
+ ci->image_loaded = 1;
+ return(0);
+}
+
+
+/****************************************************************************
+ * Release image buffers.
+ ****************************************************************************/
+void release_image(ContextInfo *ci)
+{
+ // Free RGBA format buffer
+ if (ci->rgbaFrame) av_free(ci->rgbaFrame);
+ ci->rgbaFrame = 0;
+ if (ci->inp_fmt != PIX_FMT_RGBA32)
+ {
+ if (ci->rgba_buf) av_free(ci->rgba_buf);
+ ci->rgba_buf = 0;
+ }
+
+ // Free input buffer
+ if (ci->inp_buf) av_free(ci->inp_buf);
+ ci->inp_buf = 0;
+
+ ci->image_loaded = 0;
+}
+
+
+/****************************************************************************
+ * Alloc callback for av_read_image.
+ ****************************************************************************/
+static int read_image_alloc_cb(void *opaque, AVImageInfo *info)
+{
+ ContextInfo *ci = opaque;
+
+ // Capture image dimensions and pixel format
+ if (!info->width || !info->height) return -1;
+ ci->width = info->width;
+ ci->height = info->height;
+ ci->inp_fmt = info->pix_fmt;
+
+ // Allocate input image buffer
+ ci->inp_size = avpicture_get_size(info->pix_fmt,info->width,info->height);
+ ci->inp_buf = av_malloc(ci->inp_size);
+
+ // Map input frame to buffer
+ avpicture_fill(&info->pict,ci->inp_buf,info->pix_fmt,info->width,info->height);
+
+ // Input format is already PIX_FMT_RGBA32
+ if (ci->inp_fmt == PIX_FMT_RGBA32)
+ {
+ ci->rgba_size = ci->inp_size;
+ ci->rgba_buf = ci->inp_buf;
+ }
+ // Otherwise allocate rgba buffer
+ else
+ {
+ ci->rgba_size = avpicture_get_size(PIX_FMT_RGBA32,info->width,info->height);
+ ci->rgba_buf = av_malloc(ci->rgba_size);
+ }
+
+ // Map RGBA frame to buffer
+ ci->rgbaFrame = avcodec_alloc_frame();
+ avpicture_fill((AVPicture*)ci->rgbaFrame,
+ ci->rgba_buf,PIX_FMT_RGBA32,info->width,info->height);
+
+ return(0);
+}
+
+
+/****************************************************************************
+ * Calculate and cache bounds.
+ ****************************************************************************/
+static void calc_bounds(ContextInfo *ci, int vid_width, int vid_height)
+{
+ // skip if already cached
+ if (ci->vidWidth && ci->vidHeight) return;
+
+ // Cache video frame dimensions
+ ci->vidWidth = vid_width;
+ ci->vidHeight = vid_height;
+
+ // Calculate row sizes - assume 4 bytes/pixel for RGBA
+ ci->vid_row = vid_width << 2;
+ ci->logo_row = ci->width << 2;
+
+ // Calculate logo position on frame
+ if (ci->xDir < 0)
+ {
+ ci->rLogo.right = ci->vidWidth - ci->xOff;
+ ci->rLogo.left = ci->rLogo.right - ci->width;
+ }
+ else
+ {
+ ci->rLogo.left = ci->xOff;
+ ci->rLogo.right = ci->rLogo.left + ci->width;
+ }
+ if (ci->yDir < 0)
+ {
+ ci->rLogo.bottom = ci->vidHeight - ci->yOff;
+ ci->rLogo.top = ci->rLogo.bottom - ci->height;
+ }
+ else
+ {
+ ci->rLogo.top = ci->yOff;
+ ci->rLogo.bottom = ci->rLogo.top + ci->height;
+ }
+
+ // Calculate drop shadow position on frame
+ ci->rDrop.left = ci->rLogo.left + ci->xDrop;
+ ci->rDrop.right = ci->rLogo.right + ci->xDrop;
+ ci->rDrop.top = ci->rLogo.top + ci->yDrop;
+ ci->rDrop.bottom = ci->rLogo.bottom + ci->yDrop;
+
+ // Calculate combined logo/drop-shadow bounds
+ ci->rBounds.left = max(0,min(vid_width,min(ci->rLogo.left,ci->rDrop.left)));
+ ci->rBounds.top = max(0,min(vid_height,min(ci->rLogo.top,ci->rDrop.top)));
+ ci->rBounds.right = min(vid_width,max(0,max(ci->rLogo.right,ci->rDrop.right)));
+ ci->rBounds.bottom = min(vid_height,max(0,max(ci->rLogo.bottom,ci->rDrop.bottom)));
+}
+
+
+/****************************************************************************
+ * Very simple alpha merge.
+ *
+ * alpha is 0.0 (transparent) to 1.0 (opaque).
+ ****************************************************************************/
+static int alpha_merge(int back, int fore, double alpha)
+{
+ int val = (back * (1.0 - alpha)) + (fore * alpha);
+ return(min(255,(max(0,val))));
+}
+
Index: vhook/Makefile
===================================================================
--- vhook/Makefile (revision 6691)
+++ vhook/Makefile (working copy)
@@ -6,7 +6,7 @@ CFLAGS=-I$(BUILD_ROOT) -I$(SRC_PATH) -I$
-I$(SRC_PATH)/libavformat $(VHOOKCFLAGS) -DHAVE_AV_CONFIG_H
LDFLAGS+= -g
-HOOKS=null$(SLIBSUF) fish$(SLIBSUF) ppm$(SLIBSUF) watermark$(SLIBSUF)
+HOOKS=null$(SLIBSUF) fish$(SLIBSUF) ppm$(SLIBSUF) watermark$(SLIBSUF) logo$(SLIBSUF)
ALLHOOKS=$(HOOKS) imlib2$(SLIBSUF) drawtext$(SLIBSUF)
ifeq ($(HAVE_IMLIB2),yes)
More information about the ffmpeg-devel
mailing list