[Ffmpeg-devel] logo.so module

jserv at linux2.cc.ntu.edu.tw jserv
Sat Oct 14 14:54:57 CEST 2006


On Sat, Oct 14, 2006 at 11:46:53AM +0200, Michael Niedermayer wrote:
> Hi
> 
> On Sat, Oct 14, 2006 at 10:29:29AM +0200, Tobias Marx wrote:
> > Hi guys!
> > 
> > I think you should include Bob's logo plugin:
> > 
> > http://graphcomp.com/ffmpeg/#plugins
> > 
> > into future ffmpeg releases!
> 
> read http://ffmpeg.mplayerhq.hu/ffmpeg-doc.html#SEC37 and send a patch

hi list,

  Bob's logo vhook plugin is impressive to me.

I made a patch based on Bob's work with some changes: 
  - Remove SegFault when image type is not supported.
  - Eliminate compilation warnings.
  - Code cleanup.

Please take a look over the attachment.

Best Regards,
Jim Huang
-------------- next part --------------
Index: vhook/logo.c
===================================================================
--- vhook/logo.c	(revision 0)
+++ vhook/logo.c	(revision 0)
@@ -0,0 +1,671 @@
+/**
+ * \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.
+ *
+ ******************************************************************************
+ * LICENSE:
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+
+
+/* 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 6689)
+++ 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