1/*
2 * Watermark Hook
3 * Copyright (c) 2005 Marcus Engene myfirstname(at)mylastname.se
4 *
5 * parameters for watermark:
6 *  -m nbr = nbr is 0..1. 0 is the default mode, see below.
7 *  -t nbr = nbr is six digit hex. Threshold.
8 *  -f file = file is the watermark image filename. You must specify this!
9 *
10 * MODE 0:
11 * The watermark picture works like this (assuming color intensities 0..0xff):
12 * Per color do this:
13 * If mask color is 0x80, no change to the original frame.
14 * If mask color is < 0x80 the abs difference is subtracted from the frame. If
15 * result < 0, result = 0
16 * If mask color is > 0x80 the abs difference is added to the frame. If result
17 * > 0xff, result = 0xff
18 *
19 * You can override the 0x80 level with the -t flag. E.g. if threshold is
20 * 000000 the color value of watermark is added to the destination.
21 *
22 * This way a mask that is visible both in light pictures and in dark can be
23 * made (fex by using a picture generated by Gimp and the bump map tool).
24 *
25 * An example watermark file is at
26 * http://engene.se/ffmpeg_watermark.gif
27 *
28 * MODE 1:
29 * Per color do this:
30 * If mask color > threshold color then the watermark pixel is used.
31 *
32 * Example usage:
33 *  ffmpeg -i infile -vhook '/path/watermark.so -f wm.gif' -an out.mov
34 *  ffmpeg -i infile -vhook '/path/watermark.so -f wm.gif -m 1 -t 222222' -an out.mov
35 *
36 * Note that the entire vhook argument is encapsulated in ''. This
37 * way, arguments to the vhook won't be mixed up with those for ffmpeg.
38 *
39 * This file is part of FFmpeg.
40 *
41 * FFmpeg is free software; you can redistribute it and/or
42 * modify it under the terms of the GNU Lesser General Public
43 * License as published by the Free Software Foundation; either
44 * version 2.1 of the License, or (at your option) any later version.
45 *
46 * FFmpeg is distributed in the hope that it will be useful,
47 * but WITHOUT ANY WARRANTY; without even the implied warranty of
48 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
49 * Lesser General Public License for more details.
50 *
51 * You should have received a copy of the GNU Lesser General Public
52 * License along with FFmpeg; if not, write to the Free Software
53 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
54 */
55
56#include <stdlib.h>
57//#include <fcntl.h>
58#include <unistd.h>
59#include <stdarg.h>
60
61#include "libavutil/common.h"
62#include "libavformat/avformat.h"
63#include "libavformat/framehook.h"
64#include "libswscale/swscale.h"
65
66static int sws_flags = SWS_BICUBIC;
67
68typedef struct {
69    char            filename[2000];
70    int             x_size;
71    int             y_size;
72
73    /* get_watermark_picture() variables */
74    AVFormatContext *pFormatCtx;
75    const char     *p_ext;
76    int             videoStream;
77    int             frameFinished;
78    AVCodecContext *pCodecCtx;
79    AVCodec        *pCodec;
80    AVFrame        *pFrame;
81    AVPacket        packet;
82    int             numBytes;
83    uint8_t        *buffer;
84    int             i;
85    AVInputFormat  *file_iformat;
86    AVStream       *st;
87    int             is_done;
88    AVFrame        *pFrameRGB;
89    int             thrR;
90    int             thrG;
91    int             thrB;
92    int             mode;
93
94    // This vhook first converts frame to RGB ...
95    struct SwsContext *toRGB_convert_ctx;
96    // ... then converts a watermark and applies it to the RGB frame ...
97    struct SwsContext *watermark_convert_ctx;
98    // ... and finally converts back frame from RGB to initial format
99    struct SwsContext *fromRGB_convert_ctx;
100} ContextInfo;
101
102int get_watermark_picture(ContextInfo *ci, int cleanup);
103
104
105/****************************************************************************
106 *
107 ****************************************************************************/
108void Release(void *ctx)
109{
110    ContextInfo *ci;
111    ci = (ContextInfo *) ctx;
112
113    if (ci) {
114        get_watermark_picture(ci, 1);
115        sws_freeContext(ci->toRGB_convert_ctx);
116        sws_freeContext(ci->watermark_convert_ctx);
117        sws_freeContext(ci->fromRGB_convert_ctx);
118    }
119    av_free(ctx);
120}
121
122
123/****************************************************************************
124 *
125 ****************************************************************************/
126int Configure(void **ctxp, int argc, char *argv[])
127{
128    ContextInfo *ci;
129    int c;
130    int tmp = 0;
131
132    if (0 == (*ctxp = av_mallocz(sizeof(ContextInfo)))) return -1;
133    ci = (ContextInfo *) *ctxp;
134
135    optind = 1;
136
137    // Struct is mallocz:ed so no need to reset.
138    ci->thrR = 0x80;
139    ci->thrG = 0x80;
140    ci->thrB = 0x80;
141
142    while ((c = getopt(argc, argv, "f:m:t:")) > 0) {
143        switch (c) {
144            case 'f':
145                strncpy(ci->filename, optarg, 1999);
146                ci->filename[1999] = 0;
147                break;
148            case 'm':
149                ci->mode = atoi(optarg);
150                break;
151            case 't':
152                if (1 != sscanf(optarg, "%x", &tmp)) {
153                    av_log(NULL, AV_LOG_ERROR, "Watermark: argument to -t must be a 6 digit hex number\n");
154                    return -1;
155                }
156                ci->thrR = (tmp >> 16) & 0xff;
157                ci->thrG = (tmp >> 8) & 0xff;
158                ci->thrB = (tmp >> 0) & 0xff;
159                break;
160            default:
161                av_log(NULL, AV_LOG_ERROR, "Watermark: Unrecognized argument '%s'\n", argv[optind]);
162                return -1;
163        }
164    }
165
166    //
167    if (0 == ci->filename[0]) {
168        av_log(NULL, AV_LOG_ERROR, "Watermark: There is no filename specified.\n");
169        return -1;
170    }
171
172    av_register_all();
173    return get_watermark_picture(ci, 0);
174}
175
176
177/****************************************************************************
178 * For mode 0 (the original one)
179 ****************************************************************************/
180static void Process0(void *ctx,
181              AVPicture *picture,
182              enum PixelFormat pix_fmt,
183              int src_width,
184              int src_height,
185              int64_t pts)
186{
187    ContextInfo *ci = (ContextInfo *) ctx;
188    char *buf = 0;
189    AVPicture picture1;
190    AVPicture *pict = picture;
191
192    AVFrame *pFrameRGB;
193    int xm_size;
194    int ym_size;
195
196    int x;
197    int y;
198    int offs, offsm;
199    int mpoffs;
200    uint32_t *p_pixel = 0;
201    uint32_t pixel_meck;
202    uint32_t pixel;
203    uint32_t pixelm;
204    int tmp;
205    int thrR = ci->thrR;
206    int thrG = ci->thrG;
207    int thrB = ci->thrB;
208
209    if (pix_fmt != PIX_FMT_RGB32) {
210        int size;
211
212        size = avpicture_get_size(PIX_FMT_RGB32, src_width, src_height);
213        buf = av_malloc(size);
214
215        avpicture_fill(&picture1, buf, PIX_FMT_RGB32, src_width, src_height);
216
217        // if we already got a SWS context, let's realloc if is not re-useable
218        ci->toRGB_convert_ctx = sws_getCachedContext(ci->toRGB_convert_ctx,
219                                    src_width, src_height, pix_fmt,
220                                    src_width, src_height, PIX_FMT_RGB32,
221                                    sws_flags, NULL, NULL, NULL);
222        if (ci->toRGB_convert_ctx == NULL) {
223            av_log(NULL, AV_LOG_ERROR,
224                   "Cannot initialize the toRGB conversion context\n");
225            return;
226        }
227
228// img_convert parameters are          2 first destination, then 4 source
229// sws_scale   parameters are context, 4 first source,      then 2 destination
230        sws_scale(ci->toRGB_convert_ctx,
231                 picture->data, picture->linesize, 0, src_height,
232                 picture1.data, picture1.linesize);
233
234        pict = &picture1;
235    }
236
237    /* Insert filter code here */ /* ok */
238
239    // Get me next frame
240    if (0 > get_watermark_picture(ci, 0)) {
241        return;
242    }
243    // These are the three original static variables in the ffmpeg hack.
244    pFrameRGB = ci->pFrameRGB;
245    xm_size = ci->x_size;
246    ym_size = ci->y_size;
247
248    // I'll do the *4 => <<2 crap later. Most compilers understand that anyway.
249    // According to avcodec.h PIX_FMT_RGB32 is handled in endian specific manner.
250    for (y=0; y<src_height; y++) {
251        offs = y * (src_width * 4);
252        offsm = (((y * ym_size) / src_height) * 4) * xm_size; // offsm first in maskline. byteoffs!
253        for (x=0; x<src_width; x++) {
254            mpoffs = offsm + (((x * xm_size) / src_width) * 4);
255            p_pixel = (uint32_t *)&((pFrameRGB->data[0])[mpoffs]);
256            pixelm = *p_pixel;
257            p_pixel = (uint32_t *)&((pict->data[0])[offs]);
258            pixel = *p_pixel;
259//          pixelm = *((uint32_t *)&(pFrameRGB->data[mpoffs]));
260            pixel_meck = pixel & 0xff000000;
261
262            // R
263            tmp = (int)((pixel >> 16) & 0xff) + (int)((pixelm >> 16) & 0xff) - thrR;
264            if (tmp > 255) tmp = 255;
265            if (tmp < 0) tmp = 0;
266            pixel_meck |= (tmp << 16) & 0xff0000;
267            // G
268            tmp = (int)((pixel >> 8) & 0xff) + (int)((pixelm >> 8) & 0xff) - thrG;
269            if (tmp > 255) tmp = 255;
270            if (tmp < 0) tmp = 0;
271            pixel_meck |= (tmp << 8) & 0xff00;
272            // B
273            tmp = (int)((pixel >> 0) & 0xff) + (int)((pixelm >> 0) & 0xff) - thrB;
274            if (tmp > 255) tmp = 255;
275            if (tmp < 0) tmp = 0;
276            pixel_meck |= (tmp << 0) & 0xff;
277
278
279            // test:
280            //pixel_meck = pixel & 0xff000000;
281            //pixel_meck |= (pixelm & 0x00ffffff);
282
283            *p_pixel = pixel_meck;
284
285            offs += 4;
286        } // foreach X
287    } // foreach Y
288
289
290
291
292    if (pix_fmt != PIX_FMT_RGB32) {
293        ci->fromRGB_convert_ctx = sws_getCachedContext(ci->fromRGB_convert_ctx,
294                                      src_width, src_height, PIX_FMT_RGB32,
295                                      src_width, src_height, pix_fmt,
296                                      sws_flags, NULL, NULL, NULL);
297        if (ci->fromRGB_convert_ctx == NULL) {
298            av_log(NULL, AV_LOG_ERROR,
299                   "Cannot initialize the fromRGB conversion context\n");
300            return;
301        }
302// img_convert parameters are          2 first destination, then 4 source
303// sws_scale   parameters are context, 4 first source,      then 2 destination
304        sws_scale(ci->fromRGB_convert_ctx,
305                 picture1.data, picture1.linesize, 0, src_height,
306                 picture->data, picture->linesize);
307    }
308
309    av_free(buf);
310}
311
312
313/****************************************************************************
314 * For mode 1 (the original one)
315 ****************************************************************************/
316static void Process1(void *ctx,
317              AVPicture *picture,
318              enum PixelFormat pix_fmt,
319              int src_width,
320              int src_height,
321              int64_t pts)
322{
323    ContextInfo *ci = (ContextInfo *) ctx;
324    char *buf = 0;
325    AVPicture picture1;
326    AVPicture *pict = picture;
327
328    AVFrame *pFrameRGB;
329    int xm_size;
330    int ym_size;
331
332    int x;
333    int y;
334    int offs, offsm;
335    int mpoffs;
336    uint32_t *p_pixel = 0;
337    uint32_t pixel;
338    uint32_t pixelm;
339
340    if (pix_fmt != PIX_FMT_RGB32) {
341        int size;
342
343        size = avpicture_get_size(PIX_FMT_RGB32, src_width, src_height);
344        buf = av_malloc(size);
345
346        avpicture_fill(&picture1, buf, PIX_FMT_RGB32, src_width, src_height);
347
348        // if we already got a SWS context, let's realloc if is not re-useable
349        ci->toRGB_convert_ctx = sws_getCachedContext(ci->toRGB_convert_ctx,
350                                    src_width, src_height, pix_fmt,
351                                    src_width, src_height, PIX_FMT_RGB32,
352                                    sws_flags, NULL, NULL, NULL);
353        if (ci->toRGB_convert_ctx == NULL) {
354            av_log(NULL, AV_LOG_ERROR,
355                   "Cannot initialize the toRGB conversion context\n");
356            return;
357        }
358
359// img_convert parameters are          2 first destination, then 4 source
360// sws_scale   parameters are context, 4 first source,      then 2 destination
361        sws_scale(ci->toRGB_convert_ctx,
362                 picture->data, picture->linesize, 0, src_height,
363                 picture1.data, picture1.linesize);
364
365        pict = &picture1;
366    }
367
368    /* Insert filter code here */ /* ok */
369
370    // Get me next frame
371    if (0 > get_watermark_picture(ci, 0)) {
372        return;
373    }
374    // These are the three original static variables in the ffmpeg hack.
375    pFrameRGB = ci->pFrameRGB;
376    xm_size = ci->x_size;
377    ym_size = ci->y_size;
378
379    // I'll do the *4 => <<2 crap later. Most compilers understand that anyway.
380    // According to avcodec.h PIX_FMT_RGB32 is handled in endian specific manner.
381    for (y=0; y<src_height; y++) {
382        offs = y * (src_width * 4);
383        offsm = (((y * ym_size) / src_height) * 4) * xm_size; // offsm first in maskline. byteoffs!
384        for (x=0; x<src_width; x++) {
385            mpoffs = offsm + (((x * xm_size) / src_width) * 4);
386            p_pixel = (uint32_t *)&((pFrameRGB->data[0])[mpoffs]);
387            pixelm = *p_pixel; /* watermark pixel */
388            p_pixel = (uint32_t *)&((pict->data[0])[offs]);
389            pixel = *p_pixel;
390
391            if (((pixelm >> 16) & 0xff) > ci->thrR ||
392                ((pixelm >>  8) & 0xff) > ci->thrG ||
393                ((pixelm >>  0) & 0xff) > ci->thrB)
394            {
395                *p_pixel = pixelm;
396            } else {
397                *p_pixel = pixel;
398            }
399            offs += 4;
400        } // foreach X
401    } // foreach Y
402
403    if (pix_fmt != PIX_FMT_RGB32) {
404        ci->fromRGB_convert_ctx = sws_getCachedContext(ci->fromRGB_convert_ctx,
405                                      src_width, src_height, PIX_FMT_RGB32,
406                                      src_width, src_height, pix_fmt,
407                                      sws_flags, NULL, NULL, NULL);
408        if (ci->fromRGB_convert_ctx == NULL) {
409            av_log(NULL, AV_LOG_ERROR,
410                   "Cannot initialize the fromRGB conversion context\n");
411            return;
412        }
413// img_convert parameters are          2 first destination, then 4 source
414// sws_scale   parameters are context, 4 first source,      then 2 destination
415        sws_scale(ci->fromRGB_convert_ctx,
416                 picture1.data, picture1.linesize, 0, src_height,
417                 picture->data, picture->linesize);
418    }
419
420    av_free(buf);
421}
422
423
424/****************************************************************************
425 * This is the function ffmpeg.c callbacks.
426 ****************************************************************************/
427void Process(void *ctx,
428             AVPicture *picture,
429             enum PixelFormat pix_fmt,
430             int src_width,
431             int src_height,
432             int64_t pts)
433{
434    ContextInfo *ci = (ContextInfo *) ctx;
435    if (1 == ci->mode) {
436        Process1(ctx, picture, pix_fmt, src_width, src_height, pts);
437    } else {
438        Process0(ctx, picture, pix_fmt, src_width, src_height, pts);
439    }
440}
441
442
443/****************************************************************************
444 * When cleanup == 0, we try to get the next frame. If no next frame, nothing
445 * is done.
446 *
447 * This code follows the example on
448 * http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
449 *
450 * 0 = ok, -1 = error
451 ****************************************************************************/
452int get_watermark_picture(ContextInfo *ci, int cleanup)
453{
454    if (1 == ci->is_done && 0 == cleanup) return 0;
455
456    // Yes, *pFrameRGB arguments must be null the first time otherwise it's not good..
457    // This block is only executed the first time we enter this function.
458    if (0 == ci->pFrameRGB &&
459        0 == cleanup)
460    {
461
462        /*
463         * The last three parameters specify the file format, buffer size and format
464         * parameters; by simply specifying NULL or 0 we ask libavformat to auto-detect
465         * the format and use a default buffer size. (Didn't work!)
466         */
467        if (av_open_input_file(&ci->pFormatCtx, ci->filename, NULL, 0, NULL) != 0) {
468
469            // Martin says this should not be necessary but it failed for me sending in
470            // NULL instead of file_iformat to av_open_input_file()
471            ci->i = strlen(ci->filename);
472            if (0 == ci->i) {
473                av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() No filename to watermark vhook\n");
474                return -1;
475            }
476            while (ci->i > 0) {
477                if (ci->filename[ci->i] == '.') {
478                    ci->i++;
479                    break;
480                }
481                ci->i--;
482            }
483               ci->p_ext = &(ci->filename[ci->i]);
484            ci->file_iformat = av_find_input_format (ci->p_ext);
485            if (0 == ci->file_iformat) {
486                av_log(NULL, AV_LOG_INFO, "get_watermark_picture() attempt to use image2 for [%s]\n", ci->p_ext);
487                ci->file_iformat = av_find_input_format ("image2");
488            }
489            if (0 == ci->file_iformat) {
490                av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Really failed to find iformat [%s]\n", ci->p_ext);
491                return -1;
492            }
493            // now continues the Martin template.
494
495            if (av_open_input_file(&ci->pFormatCtx, ci->filename, ci->file_iformat, 0, NULL)!=0) {
496                av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to open input file [%s]\n", ci->filename);
497                return -1;
498            }
499        }
500
501        /*
502         * This fills the streams field of the AVFormatContext with valid information.
503         */
504        if(av_find_stream_info(ci->pFormatCtx)<0) {
505            av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to find stream info\n");
506            return -1;
507        }
508
509        /*
510         * As mentioned in the introduction, we'll handle only video streams, not audio
511         * streams. To make things nice and easy, we simply use the first video stream we
512         * find.
513         */
514        ci->videoStream=-1;
515        for(ci->i = 0; ci->i < ci->pFormatCtx->nb_streams; ci->i++)
516            if(ci->pFormatCtx->streams[ci->i]->codec->codec_type==CODEC_TYPE_VIDEO)
517            {
518                ci->videoStream = ci->i;
519                break;
520            }
521        if(ci->videoStream == -1) {
522            av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to find any video stream\n");
523            return -1;
524        }
525
526        ci->st = ci->pFormatCtx->streams[ci->videoStream];
527        ci->x_size = ci->st->codec->width;
528        ci->y_size = ci->st->codec->height;
529
530        // Get a pointer to the codec context for the video stream
531        ci->pCodecCtx = ci->pFormatCtx->streams[ci->videoStream]->codec;
532
533
534        /*
535         * OK, so now we've got a pointer to the so-called codec context for our video
536         * stream, but we still have to find the actual codec and open it.
537         */
538        // Find the decoder for the video stream
539        ci->pCodec = avcodec_find_decoder(ci->pCodecCtx->codec_id);
540        if(ci->pCodec == NULL) {
541            av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to find any codec\n");
542            return -1;
543        }
544
545
546        // Open codec
547        if(avcodec_open(ci->pCodecCtx, ci->pCodec)<0) {
548            av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to open codec\n");
549            return -1;
550        }
551
552        // Hack to correct wrong frame rates that seem to be generated by some
553        // codecs
554        if (ci->pCodecCtx->time_base.den>1000 && ci->pCodecCtx->time_base.num==1)
555            ci->pCodecCtx->time_base.num=1000;
556
557        /*
558         * Allocate a video frame to store the decoded images in.
559         */
560        ci->pFrame = avcodec_alloc_frame();
561
562
563        /*
564         * The RGB image pFrameRGB (of type AVFrame *) is allocated like this:
565         */
566        // Allocate an AVFrame structure
567        ci->pFrameRGB=avcodec_alloc_frame();
568        if(ci->pFrameRGB==NULL) {
569            av_log(NULL, AV_LOG_ERROR, "get_watermark_picture() Failed to alloc pFrameRGB\n");
570            return -1;
571        }
572
573        // Determine required buffer size and allocate buffer
574        ci->numBytes = avpicture_get_size(PIX_FMT_RGB32, ci->pCodecCtx->width,
575            ci->pCodecCtx->height);
576        ci->buffer = av_malloc(ci->numBytes);
577
578        // Assign appropriate parts of buffer to image planes in pFrameRGB
579        avpicture_fill((AVPicture *)ci->pFrameRGB, ci->buffer, PIX_FMT_RGB32,
580            ci->pCodecCtx->width, ci->pCodecCtx->height);
581    }
582    // TODO loop, pingpong etc?
583    if (0 == cleanup)
584    {
585//        av_log(NULL, AV_LOG_DEBUG, "get_watermark_picture() Get a frame\n");
586        while(av_read_frame(ci->pFormatCtx, &ci->packet)>=0)
587        {
588            // Is this a packet from the video stream?
589            if(ci->packet.stream_index == ci->videoStream)
590            {
591                // Decode video frame
592                avcodec_decode_video(ci->pCodecCtx, ci->pFrame, &ci->frameFinished,
593                    ci->packet.data, ci->packet.size);
594
595                // Did we get a video frame?
596                if(ci->frameFinished)
597                {
598                    // Convert the image from its native format to RGB32
599                    ci->watermark_convert_ctx =
600                        sws_getCachedContext(ci->watermark_convert_ctx,
601                            ci->pCodecCtx->width, ci->pCodecCtx->height, ci->pCodecCtx->pix_fmt,
602                            ci->pCodecCtx->width, ci->pCodecCtx->height, PIX_FMT_RGB32,
603                            sws_flags, NULL, NULL, NULL);
604                    if (ci->watermark_convert_ctx == NULL) {
605                        av_log(NULL, AV_LOG_ERROR,
606                              "Cannot initialize the watermark conversion context\n");
607                        return -1;
608                    }
609// img_convert parameters are          2 first destination, then 4 source
610// sws_scale   parameters are context, 4 first source,      then 2 destination
611                    sws_scale(ci->watermark_convert_ctx,
612                             ci->pFrame->data, ci->pFrame->linesize, 0, ci->pCodecCtx->height,
613                             ci->pFrameRGB->data, ci->pFrameRGB->linesize);
614
615                    // Process the video frame (save to disk etc.)
616                    //fprintf(stderr,"banan() New frame!\n");
617                    //DoSomethingWithTheImage(ci->pFrameRGB);
618                    return 0;
619                }
620            }
621
622            // Free the packet that was allocated by av_read_frame
623            av_free_packet(&ci->packet);
624        }
625        ci->is_done = 1;
626        return 0;
627    } // if 0 != cleanup
628
629    if (0 != cleanup)
630    {
631        // Free the RGB image
632        av_freep(&ci->buffer);
633        av_freep(&ci->pFrameRGB);
634
635        // Close the codec
636        if (0 != ci->pCodecCtx) {
637            avcodec_close(ci->pCodecCtx);
638            ci->pCodecCtx = 0;
639        }
640
641        // Close the video file
642        if (0 != ci->pFormatCtx) {
643            av_close_input_file(ci->pFormatCtx);
644            ci->pFormatCtx = 0;
645        }
646
647        ci->is_done = 0;
648    }
649    return 0;
650}
651
652
653void parse_arg_file(const char *filename)
654{
655}
656