libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

mediaffmpeg.c (7549B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2026 Vidyut Samanta and Christian Grothoff
      4 
      5      libextractor is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      libextractor is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with libextractor; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19  */
     20 /**
     21  * @file plugins/mediaffmpeg.c
     22  * @brief plumbing between an extraction context and FFmpeg's demuxer
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "mediaffmpeg.h"
     27 
     28 
     29 /**
     30  * Implementation of AVIOContext's read callback.
     31  *
     32  * @param opaque our `struct EXTRACTOR_FFmpegIO`
     33  * @param buf where to write the data
     34  * @param buf_size number of bytes to read
     35  * @return number of bytes read, AVERROR_EOF at the end of the file
     36  */
     37 static int
     38 read_packet (void *opaque,
     39              uint8_t *buf,
     40              int buf_size)
     41 {
     42   struct EXTRACTOR_FFmpegIO *io = opaque;
     43   void *data;
     44   ssize_t ret;
     45 
     46   ret = io->ec->read (io->ec->cls,
     47                       &data,
     48                       buf_size);
     49   if (ret <= 0)
     50     return AVERROR_EOF;
     51   /* the buffer handed back by read() is only valid until the next call */
     52   memcpy (buf,
     53           data,
     54           ret);
     55   return (int) ret;
     56 }
     57 
     58 
     59 /**
     60  * Implementation of AVIOContext's seek callback.
     61  *
     62  * @param opaque our `struct EXTRACTOR_FFmpegIO`
     63  * @param offset position to seek to
     64  * @param whence SEEK_SET, SEEK_CUR, SEEK_END or AVSEEK_SIZE
     65  * @return new position, or a negative error code
     66  */
     67 static int64_t
     68 seek_packet (void *opaque,
     69              int64_t offset,
     70              int whence)
     71 {
     72   struct EXTRACTOR_FFmpegIO *io = opaque;
     73   uint64_t size;
     74 
     75   if (AVSEEK_SIZE == (whence & ~AVSEEK_FORCE))
     76   {
     77     size = io->ec->get_size (io->ec->cls);
     78     if (UINT64_MAX == size)
     79       return AVERROR (ESPIPE);
     80     return (int64_t) size;
     81   }
     82   return io->ec->seek (io->ec->cls,
     83                        offset,
     84                        whence & ~AVSEEK_FORCE);
     85 }
     86 
     87 
     88 /**
     89  * Implementation of AVIOInterruptCB; lets us get out of a decoder that is
     90  * taking too long instead of being killed by the parent process.
     91  *
     92  * @param opaque our `struct EXTRACTOR_FFmpegIO`
     93  * @return 1 if FFmpeg should abort
     94  */
     95 static int
     96 interrupt_callback (void *opaque)
     97 {
     98   struct EXTRACTOR_FFmpegIO *io = opaque;
     99 
    100   return EXTRACTOR_media_deadline_expired_ (&io->deadline);
    101 }
    102 
    103 
    104 /**
    105  * Swallow the diagnostics FFmpeg would otherwise write to stderr.
    106  *
    107  * @param ptr unused
    108  * @param level unused
    109  * @param fmt unused
    110  * @param vl unused
    111  */
    112 static void
    113 log_callback (void *ptr,
    114               int level,
    115               const char *fmt,
    116               va_list vl)
    117 {
    118   (void) ptr;
    119   (void) level;
    120   (void) fmt;
    121   (void) vl;
    122 }
    123 
    124 
    125 void
    126 EXTRACTOR_ffmpeg_silence_ ()
    127 {
    128   /* When something goes wrong in here, FFmpeg's log is the only thing
    129      that will tell you what; keep it available for debugging, since in
    130      the normal (out-of-process) case stderr is closed anyway. */
    131   if (NULL != getenv ("LIBEXTRACTOR_DEBUG"))
    132     return;
    133   av_log_set_callback (&log_callback);
    134 }
    135 
    136 
    137 int
    138 EXTRACTOR_ffmpeg_open_ (struct EXTRACTOR_FFmpegIO *io)
    139 {
    140   uint8_t *buffer;
    141 
    142   io->fmt = NULL;
    143   io->avio = NULL;
    144   if (NULL == (buffer = av_malloc (EXTRACTOR_FFMPEG_AVIO_BUFFER)))
    145     return -1;
    146   if (NULL == (io->avio = avio_alloc_context (buffer,
    147                                               EXTRACTOR_FFMPEG_AVIO_BUFFER,
    148                                               0,
    149                                               io,
    150                                               &read_packet,
    151                                               NULL,
    152                                               &seek_packet)))
    153   {
    154     av_free (buffer);
    155     return -1;
    156   }
    157   if (NULL == (io->fmt = avformat_alloc_context ()))
    158   {
    159     EXTRACTOR_ffmpeg_close_ (io);
    160     return -1;
    161   }
    162   io->fmt->pb = io->avio;
    163   io->fmt->flags |= AVFMT_FLAG_CUSTOM_IO;
    164   io->fmt->probesize = EXTRACTOR_FFMPEG_PROBE_SIZE;
    165   io->fmt->max_analyze_duration = EXTRACTOR_FFMPEG_MAX_ANALYZE;
    166   io->fmt->interrupt_callback.callback = &interrupt_callback;
    167   io->fmt->interrupt_callback.opaque = io;
    168   if (0 != avformat_open_input (&io->fmt,
    169                                 NULL,
    170                                 NULL,
    171                                 NULL))
    172   {
    173     /* avformat_open_input() freed the context for us */
    174     io->fmt = NULL;
    175     EXTRACTOR_ffmpeg_close_ (io);
    176     return -1;
    177   }
    178   if (0 > avformat_find_stream_info (io->fmt,
    179                                      NULL))
    180   {
    181     EXTRACTOR_ffmpeg_close_ (io);
    182     return -1;
    183   }
    184   return 0;
    185 }
    186 
    187 
    188 void
    189 EXTRACTOR_ffmpeg_close_ (struct EXTRACTOR_FFmpegIO *io)
    190 {
    191   if (NULL != io->fmt)
    192     avformat_close_input (&io->fmt);
    193   if (NULL != io->avio)
    194   {
    195     /* we passed AVFMT_FLAG_CUSTOM_IO, so this is still ours to free; note
    196        that avio may have replaced the buffer we originally handed it */
    197     av_free (io->avio->buffer);
    198     avio_context_free (&io->avio);
    199   }
    200 }
    201 
    202 
    203 int
    204 EXTRACTOR_ffmpeg_open_stream_ (struct EXTRACTOR_FFmpegIO *io,
    205                                enum AVMediaType type,
    206                                AVCodecContext **ctx)
    207 {
    208   const AVCodec *codec;
    209   AVCodecContext *cc;
    210   int stream;
    211 
    212   *ctx = NULL;
    213   stream = av_find_best_stream (io->fmt,
    214                                 type,
    215                                 -1,
    216                                 -1,
    217                                 &codec,
    218                                 0);
    219   if ( (0 > stream) ||
    220        (NULL == codec) )
    221     return -1;
    222   if (NULL == (cc = avcodec_alloc_context3 (codec)))
    223     return -1;
    224   if (0 > avcodec_parameters_to_context (cc,
    225                                          io->fmt->streams[stream]->codecpar))
    226   {
    227     avcodec_free_context (&cc);
    228     return -1;
    229   }
    230   /* one thread: we are in a forked child that is expected to talk to its
    231      parent regularly, and there is nothing here worth parallelizing */
    232   cc->thread_count = 1;
    233   if (0 != avcodec_open2 (cc, codec, NULL))
    234   {
    235     avcodec_free_context (&cc);
    236     return -1;
    237   }
    238   *ctx = cc;
    239   return stream;
    240 }
    241 
    242 
    243 double
    244 EXTRACTOR_ffmpeg_seek_ (struct EXTRACTOR_FFmpegIO *io,
    245                         int stream,
    246                         AVCodecContext *ctx,
    247                         unsigned int offset)
    248 {
    249   double duration;
    250   double target;
    251   int64_t ts;
    252 
    253   duration = (AV_NOPTS_VALUE == io->fmt->duration)
    254              ? -1.0
    255              : ((double) io->fmt->duration) / AV_TIME_BASE;
    256   target = offset;
    257   if ( (duration > 0.0) &&
    258        (target > duration / 3.0) )
    259     target = duration / 3.0;
    260   if ( (target <= 1.0) ||
    261        (duration <= 0.0) )
    262     return (target <= 1.0) ? 0.0 : target;
    263   ts = av_rescale_q ((int64_t) (target * AV_TIME_BASE),
    264                      AV_TIME_BASE_Q,
    265                      io->fmt->streams[stream]->time_base);
    266   if (0 > av_seek_frame (io->fmt,
    267                          stream,
    268                          ts,
    269                          AVSEEK_FLAG_BACKWARD))
    270     return 0.0;   /* seeking failed; caller decodes from wherever we are */
    271   avcodec_flush_buffers (ctx);
    272   return target;
    273 }
    274 
    275 
    276 /* end of mediaffmpeg.c */