thumbnailextractorffmpeg.c

Go to the documentation of this file.
00001 /*
00002      This file is part of libextractor.
00003      Copyright (C) 2008 Heikki Lindholm
00004 
00005      libextractor is free software; you can redistribute it and/or modify
00006      it under the terms of the GNU General Public License as published
00007      by the Free Software Foundation; either version 2, or (at your
00008      option) any later version.
00009 
00010      libextractor is distributed in the hope that it will be useful, but
00011      WITHOUT ANY WARRANTY; without even the implied warranty of
00012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013      General Public License for more details.
00014 
00015      You should have received a copy of the GNU General Public License
00016      along with libextractor; see the file COPYING.  If not, write to the
00017      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00018      Boston, MA 02111-1307, USA.
00019  */
00020 
00021 /**
00022  * @file thumbnailextractorffmpeg.c
00023  * @author Heikki Lindholm
00024  * @brief this extractor produces a binary encoded
00025  * thumbnail of images and videos using the ffmpeg libs.
00026  */
00027 
00028 #include "platform.h"
00029 #include "extractor.h"
00030 
00031 #include <avformat.h>
00032 #include <avcodec.h>
00033 #include <swscale.h>
00034 
00035 #define DEBUG 0
00036 
00037 struct StreamDescriptor
00038 {
00039   const uint8_t *data;
00040   size_t offset;
00041   size_t size;
00042 };
00043 
00044 
00045 void __attribute__ ((constructor)) ffmpeg_lib_init (void)
00046 {
00047 #if DEBUG
00048   printf ("av_register_all()\n");
00049 #endif
00050   av_register_all ();
00051 }
00052 
00053 static int
00054 stream_read (void *opaque, uint8_t * buf, int buf_size)
00055 {
00056   struct StreamDescriptor *rs = (struct StreamDescriptor *) opaque;
00057   size_t len;
00058 #if DEBUG
00059   printf ("read_packet: %zu\n", buf_size);
00060 #endif
00061   if (rs)
00062     {
00063       if (rs->data == NULL)
00064         return -1;
00065       if (rs->offset >= rs->size)
00066         return 0;
00067       len = buf_size;
00068       if (rs->offset + len > rs->size)
00069         len = rs->size - rs->offset;
00070 
00071       memcpy (buf, rs->data + rs->offset, len);
00072       rs->offset += len;
00073 #if DEBUG
00074       printf ("read_packet: len: %zu\n", len);
00075 #endif
00076       return len;
00077     }
00078   return -1;
00079 }
00080 
00081 static offset_t
00082 stream_seek (void *opaque, offset_t offset, int whence)
00083 {
00084   struct StreamDescriptor *rs = (struct StreamDescriptor *) opaque;
00085   offset_t off_abs;
00086 #if DEBUG
00087   printf ("my_seek: %lld %d\n", offset, whence);
00088 #endif
00089   if (rs)
00090     {
00091       if (whence == AVSEEK_SIZE)
00092         return (offset_t) rs->size;
00093       else if (whence == SEEK_CUR)
00094         off_abs = (offset_t) rs->offset + offset;
00095       else if (whence == SEEK_SET)
00096         off_abs = offset;
00097       else if (whence == SEEK_END)
00098         off_abs = (offset_t) rs->size + offset;
00099       else
00100         {
00101           printf ("whence error %d\n", whence);
00102           abort ();
00103           return AVERROR (EINVAL);
00104         }
00105       if (off_abs >= 0 && off_abs < (offset_t) rs->size)
00106         rs->offset = (size_t) off_abs;
00107       else
00108         off_abs = AVERROR (EINVAL);
00109       return off_abs;
00110     }
00111   return -1;
00112 }
00113 
00114 static EXTRACTOR_KeywordList *
00115 addKeyword (EXTRACTOR_KeywordType type,
00116             char *keyword, EXTRACTOR_KeywordList * next)
00117 {
00118   EXTRACTOR_KeywordList *result;
00119 
00120   if (keyword == NULL)
00121     return next;
00122   result = malloc (sizeof (EXTRACTOR_KeywordList));
00123   result->next = next;
00124   result->keyword = keyword;
00125   result->keywordType = type;
00126   return result;
00127 }
00128 
00129 
00130 struct MimeToDecoderMapping
00131 {
00132   const char *mime_type;
00133   enum CodecID codec_id;
00134 };
00135 
00136 /* map mime image types to a decoder */
00137 static const struct MimeToDecoderMapping m2d_map[] = {
00138   {"image/x-bmp", CODEC_ID_BMP},
00139   {"image/gif", CODEC_ID_GIF},
00140   {"image/jpeg", CODEC_ID_MJPEG},
00141   {"image/png", CODEC_ID_PNG},
00142   {"image/x-portable-pixmap", CODEC_ID_PPM},
00143   {NULL, CODEC_ID_NONE}
00144 };
00145 
00146 #define PROBE_MAX (1<<20)
00147 #define BIOBUF_SIZE (64*1024)
00148 #define THUMBSIZE 128           /* max dimension in pixels */
00149 #define MAX_THUMB_SIZE (100*1024)       /* in bytes */
00150 
00151 struct EXTRACTOR_Keywords *
00152 libextractor_thumbnailffmpeg_extract (const char *filename,
00153                                       const unsigned char *data,
00154                                       size_t size,
00155                                       struct EXTRACTOR_Keywords *prev)
00156 {
00157   int score;
00158 
00159   AVInputFormat *fmt;
00160   AVProbeData pdat;
00161 
00162   ByteIOContext *bio_ctx = NULL;
00163   uint8_t *bio_buffer;
00164   struct StreamDescriptor reader_state;
00165 
00166   AVFormatContext *format_ctx = NULL;
00167   AVCodecContext *codec_ctx = NULL;
00168   AVPacket packet;
00169   int video_stream_index;
00170   AVCodec *codec;
00171   AVFrame *frame = NULL;
00172   AVFrame *thumb_frame = NULL;
00173   int64_t ts;
00174 
00175   struct SwsContext *scaler_ctx;
00176   int sws_flags = SWS_BILINEAR;
00177   uint8_t *thumb_buffer;
00178   int thumb_width, thumb_height;
00179   int sar_num, sar_den;
00180 
00181   uint8_t *encoder_output_buffer;
00182   size_t encoder_output_buffer_size;
00183   AVCodecContext *enc_codec_ctx;
00184   AVCodec *enc_codec;
00185 
00186   int i;
00187   int err;
00188   int frame_finished;
00189 
00190   char *binary;
00191   const char *mime;
00192   int is_image;
00193   enum CodecID image_codec_id;
00194 
00195   bio_ctx = NULL;
00196   bio_buffer = NULL;
00197   format_ctx = NULL;
00198   codec = NULL;
00199   frame = NULL;
00200   thumb_frame = NULL;
00201   thumb_buffer = NULL;
00202   scaler_ctx = NULL;
00203   encoder_output_buffer = NULL;
00204   enc_codec = NULL;
00205   enc_codec_ctx = NULL;
00206 
00207   is_image = 0;
00208 
00209   mime = EXTRACTOR_extractLast (EXTRACTOR_MIMETYPE, prev);
00210   if (mime != NULL)
00211     {
00212       i = 0;
00213       while (m2d_map[i].mime_type != NULL)
00214         {
00215           if (!strcmp (m2d_map[i].mime_type, mime))
00216             {
00217               is_image = 1;
00218               image_codec_id = m2d_map[i].codec_id;
00219               break;
00220             }
00221           i++;
00222         }
00223     }
00224 
00225 #if DEBUG
00226   printf ("is_image: %d codec:%d\n", is_image, image_codec_id);
00227 #endif
00228   if (!is_image)
00229     {
00230       pdat.filename = filename;
00231       pdat.buf = (unsigned char *) data;
00232       pdat.buf_size = (size > PROBE_MAX) ? PROBE_MAX : size;
00233 
00234       fmt = av_probe_input_format (&pdat, 1);
00235       if (fmt == NULL)
00236         return prev;
00237 #if DEBUG
00238       printf ("format %p [%s] [%s]\n", fmt, fmt->name, fmt->long_name);
00239 #endif
00240       pdat.buf = (unsigned char *) data;
00241       pdat.buf_size = size > PROBE_MAX ? PROBE_MAX : size;
00242       score = fmt->read_probe (&pdat);
00243 #if DEBUG
00244       printf ("score: %d\n", score);
00245 #endif
00246       /*if (score < 50) return prev; */
00247     }
00248 
00249   if (is_image)
00250     {
00251       codec_ctx = avcodec_alloc_context ();
00252       codec = avcodec_find_decoder (image_codec_id);
00253       if (codec != NULL)
00254         {
00255           if (avcodec_open (codec_ctx, codec) != 0)
00256             {
00257 #if DEBUG
00258               printf ("open codec failed\n");
00259 #endif
00260               codec = NULL;
00261             }
00262         }
00263     }
00264   else
00265     {
00266       bio_ctx = malloc (sizeof (ByteIOContext));
00267       bio_buffer = malloc (BIOBUF_SIZE);
00268 
00269       reader_state.data = data;
00270       reader_state.offset = 0;
00271       reader_state.size = size;
00272 
00273       init_put_byte (bio_ctx, bio_buffer,
00274                      BIOBUF_SIZE, 0, &reader_state,
00275                      stream_read, NULL, stream_seek);
00276 
00277       fmt->flags |= AVFMT_NOFILE;
00278       err = av_open_input_stream (&format_ctx, bio_ctx, "", fmt, NULL);
00279       if (err < 0)
00280         {
00281 #if DEBUG
00282           printf ("couldn't open input stream\n");
00283 #endif
00284           goto out;
00285         }
00286 
00287       err = av_find_stream_info (format_ctx);
00288       if (err < 0)
00289         {
00290 #if DEBUG
00291           printf ("couldn't find codec params\n");
00292 #endif
00293           goto out;
00294         }
00295 
00296       for (i = 0; i < format_ctx->nb_streams; i++)
00297         {
00298           codec_ctx = format_ctx->streams[i]->codec;
00299           if (codec_ctx->codec_type == CODEC_TYPE_VIDEO)
00300             {
00301               video_stream_index = i;
00302               codec = avcodec_find_decoder (codec_ctx->codec_id);
00303               if (codec == NULL)
00304                 {
00305 #if DEBUG
00306                   printf ("find_decoder failed\n");
00307 #endif
00308                   break;
00309                 }
00310               err = avcodec_open (codec_ctx, codec);
00311               if (err != 0)
00312                 {
00313 #if DEBUG
00314                   printf ("failed to open codec\n");
00315 #endif
00316                   codec = NULL;
00317                 }
00318               break;
00319             }
00320         }
00321     }
00322 
00323   if (codec_ctx == NULL || codec == NULL)
00324     {
00325 #if DEBUG
00326       printf ("failed to open codec");
00327 #endif
00328       goto out;
00329     }
00330   frame = avcodec_alloc_frame ();
00331   if (frame == NULL)
00332     {
00333 #if DEBUG
00334       printf ("failed to alloc frame");
00335 #endif
00336       goto out;
00337     }
00338 
00339   if (!is_image)
00340     {
00341 #if DEBUG
00342       printf ("duration: %lld\n", format_ctx->duration);
00343       if (format_ctx->duration == AV_NOPTS_VALUE)
00344         printf ("duration unknown\n");
00345 #endif
00346       /* TODO: if duration is known seek to to some better place(?) */
00347       ts = 10;                  // s
00348       ts = ts * AV_TIME_BASE;
00349       err = av_seek_frame (format_ctx, -1, ts, 0);
00350       if (err >= 0)
00351         {
00352           avcodec_flush_buffers (codec_ctx);
00353         }
00354 #if DEBUG
00355       else
00356         printf ("seeking failed %d\n", err);
00357 #endif
00358     }
00359 
00360   frame_finished = 0;
00361   if (is_image)
00362     {
00363       avcodec_decode_video (codec_ctx, frame, &frame_finished, data, size);
00364     }
00365   else
00366     {
00367       while (1)
00368         {
00369           err = av_read_frame (format_ctx, &packet);
00370           if (err < 0)
00371             break;
00372           if (packet.stream_index == video_stream_index)
00373             {
00374               avcodec_decode_video (codec_ctx,
00375                                     frame,
00376                                     &frame_finished,
00377                                     packet.data, packet.size);
00378               if (frame_finished && frame->key_frame)
00379                 {
00380                   av_free_packet (&packet);
00381                   break;
00382                 }
00383             }
00384           av_free_packet (&packet);
00385         }
00386     }
00387 
00388   if (!frame_finished || codec_ctx->width == 0 || codec_ctx->height == 0)
00389     goto out;
00390 
00391   sar_num = codec_ctx->sample_aspect_ratio.num;
00392   sar_den = codec_ctx->sample_aspect_ratio.den;
00393   if (sar_num <= 0 || sar_den <= 0)
00394     {
00395       sar_num = 1;
00396       sar_den = 1;
00397     }
00398   if ((codec_ctx->width * sar_num) / sar_den > codec_ctx->height)
00399     {
00400       thumb_width = THUMBSIZE;
00401       thumb_height = (thumb_width * codec_ctx->height) /
00402         ((codec_ctx->width * sar_num) / sar_den);
00403     }
00404   else
00405     {
00406       thumb_height = THUMBSIZE;
00407       thumb_width = (thumb_height *
00408                      ((codec_ctx->width * sar_num) / sar_den)) /
00409         codec_ctx->height;
00410     }
00411   if (thumb_width < 8)
00412     thumb_width = 8;
00413   if (thumb_height < 1)
00414     thumb_height = 1;
00415 #if DEBUG
00416   printf ("thumb dim: %d %d\n", thumb_width, thumb_height);
00417 #endif
00418 
00419   scaler_ctx =
00420     sws_getContext (codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,
00421                     thumb_width, thumb_height, PIX_FMT_RGB24, sws_flags, NULL,
00422                     NULL, NULL);
00423   if (scaler_ctx == NULL)
00424     {
00425 #if DEBUG
00426       printf ("failed to alloc scaler\n");
00427 #endif
00428       goto out;
00429     }
00430   thumb_frame = avcodec_alloc_frame ();
00431   thumb_buffer =
00432     av_malloc (avpicture_get_size (PIX_FMT_RGB24, thumb_width, thumb_height));
00433   if (thumb_frame == NULL || thumb_buffer == NULL)
00434     {
00435 #if DEBUG
00436       printf ("failed to alloc thumb frame\n");
00437 #endif
00438       goto out;
00439     }
00440   avpicture_fill ((AVPicture *) thumb_frame, thumb_buffer,
00441                   PIX_FMT_RGB24, thumb_width, thumb_height);
00442 
00443   sws_scale (scaler_ctx,
00444              frame->data, frame->linesize,
00445              0, codec_ctx->height, thumb_frame->data, thumb_frame->linesize);
00446 
00447   encoder_output_buffer_size = MAX_THUMB_SIZE;
00448   encoder_output_buffer = av_malloc (encoder_output_buffer_size);
00449   if (encoder_output_buffer == NULL)
00450     {
00451 #if DEBUG
00452       printf ("couldn't alloc encoder output buf\n");
00453 #endif
00454       goto out;
00455     }
00456 
00457   enc_codec = avcodec_find_encoder_by_name ("png");
00458   if (enc_codec == NULL)
00459     {
00460 #if DEBUG
00461       printf ("couldn't find encoder\n");
00462 #endif
00463       goto out;
00464     }
00465   enc_codec_ctx = avcodec_alloc_context ();
00466   enc_codec_ctx->width = thumb_width;
00467   enc_codec_ctx->height = thumb_height;
00468   enc_codec_ctx->pix_fmt = PIX_FMT_RGB24;
00469 
00470   if (avcodec_open (enc_codec_ctx, enc_codec) < 0)
00471     {
00472 #if DEBUG
00473       printf ("couldn't open encoder\n");
00474 #endif
00475       enc_codec = NULL;
00476       goto out;
00477     }
00478 
00479   err = avcodec_encode_video (enc_codec_ctx,
00480                               encoder_output_buffer,
00481                               encoder_output_buffer_size, thumb_frame);
00482   if (err <= 0)
00483     goto out;
00484 
00485   binary =
00486     EXTRACTOR_binaryEncode ((const unsigned char *) encoder_output_buffer,
00487                             err);
00488   if (binary != NULL)
00489     prev = addKeyword (EXTRACTOR_THUMBNAIL_DATA, binary, prev);
00490 
00491 out:
00492   if (enc_codec != NULL)
00493     avcodec_close (enc_codec_ctx);
00494   if (enc_codec_ctx != NULL)
00495     av_free (enc_codec_ctx);
00496   if (encoder_output_buffer != NULL)
00497     av_free (encoder_output_buffer);
00498   if (scaler_ctx != NULL)
00499     sws_freeContext(scaler_ctx);
00500   if (codec != NULL)
00501     avcodec_close (codec_ctx);
00502   if (format_ctx != NULL)
00503     av_close_input_file (format_ctx);
00504   if (frame != NULL)
00505     av_free (frame);
00506   if (thumb_buffer != NULL)
00507     av_free (thumb_buffer);
00508   if (thumb_frame != NULL)
00509     av_free (thumb_frame);
00510   if (bio_ctx != NULL)
00511     free (bio_ctx);
00512   if (bio_buffer != NULL)
00513     free (bio_buffer);
00514 
00515   return prev;
00516 }
00517 
00518 struct EXTRACTOR_Keywords *
00519 libextractor_thumbnail_extract (const char *filename,
00520                                 const unsigned char *data,
00521                                 size_t size,
00522                                 struct EXTRACTOR_Keywords *prev,
00523                                 const char *options)
00524 {
00525   return libextractor_thumbnailffmpeg_extract (filename, data, size, prev);
00526 }
00527 
00528 /* end of thumbnailextractorffmpeg.c */

Generated on Thu Dec 4 09:45:13 2008 for libextractor by  doxygen 1.5.1