libextractor

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

previewopus_extractor.c (16031B)


      1 /*
      2      This file is part of libextractor.
      3      Copyright (C) 2013, 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/previewopus_extractor.c
     22  * @author Bruno Cabral
     23  * @author Christian Grothoff
     24  * @brief this extractor produces a short audio preview: about 15 seconds
     25  *        taken from the middle of the track, re-encoded as Opus in an Ogg
     26  *        container, which is the audio equivalent of a thumbnail
     27  *
     28  * Uses FFmpeg for demuxing, decoding and resampling and libopus (through
     29  * FFmpeg) for encoding.  Requires FFmpeg 5.1 or newer; there is
     30  * deliberately no compatibility code for older versions, since that is
     31  * what got the previous incarnation of this plugin deleted in 2021.
     32  */
     33 #include "platform.h"
     34 #include "extractor.h"
     35 #include "mediautil.h"
     36 #include "mediaffmpeg.h"
     37 
     38 #include <libavutil/opt.h>
     39 #include <libswresample/swresample.h>
     40 
     41 /**
     42  * Sample rate we encode at.  Opus works internally at 48kHz, so anything
     43  * else would just mean resampling twice.
     44  */
     45 #define OUTPUT_SAMPLE_RATE 48000
     46 
     47 /**
     48  * Length of the fade-in and fade-out, in milliseconds.  Without this a
     49  * preview cut from the middle of a track starts with a click.
     50  */
     51 #define FADE_MS 250
     52 
     53 /**
     54  * Refuse to emit a preview shorter than this many milliseconds.
     55  */
     56 #define MIN_PREVIEW_MS 500
     57 
     58 /**
     59  * Give up after this many decoded packets.
     60  */
     61 #define MAX_PACKETS 100000
     62 
     63 
     64 /**
     65  * State of the Opus encoder and its Ogg muxer.
     66  */
     67 struct Encoder
     68 {
     69   /**
     70    * Muxer producing the Ogg stream.
     71    */
     72   AVFormatContext *oc;
     73 
     74   /**
     75    * The Opus encoder.
     76    */
     77   AVCodecContext *ctx;
     78 
     79   /**
     80    * Stream in @e oc.
     81    */
     82   AVStream *st;
     83 };
     84 
     85 
     86 /**
     87  * Decode the audio stream into 48kHz mono 16 bit PCM, starting at
     88  * @a target seconds and stopping after @a max_samples samples.
     89  *
     90  * @param io open I/O state
     91  * @param ctx open decoder for @a stream
     92  * @param stream index of the audio stream
     93  * @param target position we are aiming for, in seconds
     94  * @param max_samples size of @a pcm in samples
     95  * @param[out] pcm where to write the samples
     96  * @return number of samples written
     97  */
     98 static size_t
     99 decode_pcm (struct EXTRACTOR_FFmpegIO *io,
    100             AVCodecContext *ctx,
    101             int stream,
    102             double target,
    103             size_t max_samples,
    104             int16_t *pcm)
    105 {
    106   struct SwrContext *swr = NULL;
    107   AVChannelLayout mono = AV_CHANNEL_LAYOUT_MONO;
    108   AVPacket *pkt;
    109   AVFrame *frame;
    110   AVRational tb = io->fmt->streams[stream]->time_base;
    111   size_t have = 0;
    112   unsigned int packets = 0;
    113 
    114   if (NULL == (pkt = av_packet_alloc ()))
    115     return 0;
    116   if (NULL == (frame = av_frame_alloc ()))
    117   {
    118     av_packet_free (&pkt);
    119     return 0;
    120   }
    121   while ( (have < max_samples) &&
    122           (packets < MAX_PACKETS) )
    123   {
    124     int r;
    125 
    126     if (EXTRACTOR_media_deadline_expired_ (&io->deadline))
    127       break;
    128     packets++;
    129     r = av_read_frame (io->fmt, pkt);
    130     if (0 != r)
    131     {
    132       avcodec_send_packet (ctx, NULL);
    133     }
    134     else if (pkt->stream_index != stream)
    135     {
    136       av_packet_unref (pkt);
    137       continue;
    138     }
    139     else if (0 != avcodec_send_packet (ctx, pkt))
    140     {
    141       av_packet_unref (pkt);
    142       continue;
    143     }
    144     av_packet_unref (pkt);
    145     while (0 == avcodec_receive_frame (ctx, frame))
    146     {
    147       uint8_t *out[1];
    148       int room;
    149       int got;
    150       double ts;
    151 
    152       ts = (AV_NOPTS_VALUE == frame->best_effort_timestamp)
    153            ? -1.0
    154            : frame->best_effort_timestamp * av_q2d (tb);
    155       if ( (ts >= 0.0) &&
    156            (ts + frame->nb_samples / (double) ((frame->sample_rate > 0)
    157                                                 ? frame->sample_rate
    158                                                : OUTPUT_SAMPLE_RATE) < target) )
    159       {
    160         /* still before the segment we want */
    161         av_frame_unref (frame);
    162         continue;
    163       }
    164       if (NULL == swr)
    165       {
    166         if (0 != swr_alloc_set_opts2 (&swr,
    167                                       &mono,
    168                                       AV_SAMPLE_FMT_S16,
    169                                       OUTPUT_SAMPLE_RATE,
    170                                       &frame->ch_layout,
    171                                       frame->format,
    172                                       frame->sample_rate,
    173                                       0,
    174                                       NULL))
    175         {
    176           av_frame_unref (frame);
    177           goto done;
    178         }
    179         if (0 > swr_init (swr))
    180         {
    181           av_frame_unref (frame);
    182           goto done;
    183         }
    184       }
    185       room = (int) (max_samples - have);
    186       out[0] = (uint8_t *) &pcm[have];
    187       got = swr_convert (swr,
    188                          out,
    189                          room,
    190                          (const uint8_t **) frame->extended_data,
    191                          frame->nb_samples);
    192       av_frame_unref (frame);
    193       if (got <= 0)
    194         continue;
    195       have += (size_t) got;
    196       if (have >= max_samples)
    197         goto done;
    198     }
    199     if (0 != r)
    200       break;
    201   }
    202 done:
    203   if (NULL != swr)
    204     swr_free (&swr);
    205   av_frame_free (&frame);
    206   av_packet_free (&pkt);
    207   return have;
    208 }
    209 
    210 
    211 /**
    212  * Set up the Opus encoder and the Ogg muxer, writing into a dynamic
    213  * memory buffer.
    214  *
    215  * @param opt plugin options (bitrate)
    216  * @param[out] enc encoder state to initialize
    217  * @return 0 on success
    218  */
    219 static int
    220 encoder_start (const struct EXTRACTOR_MediaOptions *opt,
    221                struct Encoder *enc)
    222 {
    223   const AVCodec *codec;
    224   AVChannelLayout mono = AV_CHANNEL_LAYOUT_MONO;
    225   int experimental = 0;
    226 
    227   memset (enc, 0, sizeof (*enc));
    228   codec = avcodec_find_encoder_by_name ("libopus");
    229   if (NULL == codec)
    230   {
    231     /* FFmpeg's own Opus encoder is still marked experimental, but it is
    232        better than emitting nothing at all */
    233     codec = avcodec_find_encoder (AV_CODEC_ID_OPUS);
    234     experimental = 1;
    235   }
    236   if (NULL == codec)
    237     return -1;
    238   if (0 > avformat_alloc_output_context2 (&enc->oc,
    239                                           NULL,
    240                                           "ogg",
    241                                           NULL))
    242     return -1;
    243   if (NULL == enc->oc)
    244     return -1;
    245   if (NULL == (enc->ctx = avcodec_alloc_context3 (codec)))
    246     return -1;
    247   enc->ctx->sample_rate = OUTPUT_SAMPLE_RATE;
    248   enc->ctx->bit_rate = opt->bitrate;
    249   enc->ctx->time_base = (AVRational) { 1, OUTPUT_SAMPLE_RATE };
    250   enc->ctx->thread_count = 1;
    251   if (0 != av_channel_layout_copy (&enc->ctx->ch_layout,
    252                                    &mono))
    253     return -1;
    254   if (experimental)
    255   {
    256     enc->ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    257     enc->ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
    258   }
    259   else
    260   {
    261     enc->ctx->sample_fmt = AV_SAMPLE_FMT_S16;
    262   }
    263   /* the Ogg muxer needs the OpusHead/OpusTags headers as extradata */
    264   enc->ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    265   /* libopus defaults to unconstrained VBR, where the bitrate we asked for
    266      is little more than a suggestion -- a pure tone happily comes out at
    267      twice the target.  We care about the size of the result, so ask for
    268      the constrained variant.  Not an option on the native encoder, which
    269      simply ignores it. */
    270   if (NULL != enc->ctx->priv_data)
    271     (void) av_opt_set (enc->ctx->priv_data,
    272                        "vbr",
    273                        "constrained",
    274                        0);
    275   if (0 != avcodec_open2 (enc->ctx, codec, NULL))
    276     return -1;
    277   if (NULL == (enc->st = avformat_new_stream (enc->oc, NULL)))
    278     return -1;
    279   enc->st->time_base = enc->ctx->time_base;
    280   if (0 > avcodec_parameters_from_context (enc->st->codecpar,
    281                                            enc->ctx))
    282     return -1;
    283   if (0 != avio_open_dyn_buf (&enc->oc->pb))
    284     return -1;
    285   if (0 > avformat_write_header (enc->oc, NULL))
    286     return -1;
    287   return 0;
    288 }
    289 
    290 
    291 /**
    292  * Hand one packet from the encoder to the muxer.
    293  *
    294  * @param enc encoder state
    295  * @param pkt packet to write, unreferenced on return
    296  * @return 0 on success
    297  */
    298 static int
    299 encoder_write (struct Encoder *enc,
    300                AVPacket *pkt)
    301 {
    302   av_packet_rescale_ts (pkt,
    303                         enc->ctx->time_base,
    304                         enc->st->time_base);
    305   pkt->stream_index = enc->st->index;
    306   return (0 > av_interleaved_write_frame (enc->oc, pkt)) ? -1 : 0;
    307 }
    308 
    309 
    310 /**
    311  * Encode @a num_samples of 48kHz mono PCM and finish the Ogg stream.
    312  *
    313  * @param enc encoder state
    314  * @param pcm samples to encode
    315  * @param num_samples number of samples in @a pcm
    316  * @param[out] output set to the Ogg stream, to be freed with av_free()
    317  * @param[out] output_size set to the number of bytes in @a output
    318  * @return 0 on success
    319  */
    320 static int
    321 encoder_finish (struct Encoder *enc,
    322                 const int16_t *pcm,
    323                 size_t num_samples,
    324                 uint8_t **output,
    325                 size_t *output_size)
    326 {
    327   AVFrame *frame = NULL;
    328   AVPacket *pkt = NULL;
    329   size_t pos = 0;
    330   int frame_size;
    331   int ret = -1;
    332   int size;
    333 
    334   frame_size = (enc->ctx->frame_size > 0)
    335                ? enc->ctx->frame_size
    336                : OUTPUT_SAMPLE_RATE / 50;
    337   if (NULL == (pkt = av_packet_alloc ()))
    338     goto cleanup;
    339   if (NULL == (frame = av_frame_alloc ()))
    340     goto cleanup;
    341   frame->format = enc->ctx->sample_fmt;
    342   frame->sample_rate = OUTPUT_SAMPLE_RATE;
    343   frame->nb_samples = frame_size;
    344   if (0 != av_channel_layout_copy (&frame->ch_layout,
    345                                    &enc->ctx->ch_layout))
    346     goto cleanup;
    347   if (0 != av_frame_get_buffer (frame, 0))
    348     goto cleanup;
    349   while (pos < num_samples)
    350   {
    351     size_t n = num_samples - pos;
    352     int r;
    353 
    354     if (n > (size_t) frame_size)
    355       n = frame_size;
    356     if (0 != av_frame_make_writable (frame))
    357       goto cleanup;
    358     if (AV_SAMPLE_FMT_S16 == enc->ctx->sample_fmt)
    359     {
    360       memcpy (frame->data[0],
    361               &pcm[pos],
    362               n * sizeof (int16_t));
    363       if (n < (size_t) frame_size)
    364         memset (frame->data[0] + n * sizeof (int16_t),
    365                 0,
    366                 (frame_size - n) * sizeof (int16_t));
    367     }
    368     else
    369     {
    370       float *dst = (float *) frame->data[0];
    371       size_t i;
    372 
    373       for (i = 0; i < n; i++)
    374         dst[i] = pcm[pos + i] / 32768.0f;
    375       for (i = n; i < (size_t) frame_size; i++)
    376         dst[i] = 0.0f;
    377     }
    378     frame->pts = (int64_t) pos;
    379     if (0 != avcodec_send_frame (enc->ctx, frame))
    380       goto cleanup;
    381     while (0 == (r = avcodec_receive_packet (enc->ctx, pkt)))
    382       if (0 != encoder_write (enc, pkt))
    383         goto cleanup;
    384     if (AVERROR (EAGAIN) != r)
    385       goto cleanup;
    386     pos += n;
    387   }
    388   if (0 != avcodec_send_frame (enc->ctx, NULL))
    389     goto cleanup;
    390   while (0 == avcodec_receive_packet (enc->ctx, pkt))
    391     if (0 != encoder_write (enc, pkt))
    392       goto cleanup;
    393   if (0 > av_write_trailer (enc->oc))
    394     goto cleanup;
    395   size = avio_close_dyn_buf (enc->oc->pb,
    396                              output);
    397   enc->oc->pb = NULL;
    398   if (size <= 0)
    399   {
    400     if (NULL != *output)
    401       av_free (*output);
    402     *output = NULL;
    403     goto cleanup;
    404   }
    405   *output_size = (size_t) size;
    406   ret = 0;
    407 cleanup:
    408   if (NULL != frame)
    409     av_frame_free (&frame);
    410   if (NULL != pkt)
    411     av_packet_free (&pkt);
    412   return ret;
    413 }
    414 
    415 
    416 /**
    417  * Clean up the encoder state.
    418  *
    419  * @param enc encoder state to free
    420  */
    421 static void
    422 encoder_cleanup (struct Encoder *enc)
    423 {
    424   if (NULL != enc->ctx)
    425     avcodec_free_context (&enc->ctx);
    426   if (NULL != enc->oc)
    427   {
    428     if (NULL != enc->oc->pb)
    429     {
    430       uint8_t *leftover = NULL;
    431 
    432       (void) avio_close_dyn_buf (enc->oc->pb,
    433                                  &leftover);
    434       enc->oc->pb = NULL;
    435       if (NULL != leftover)
    436         av_free (leftover);
    437     }
    438     avformat_free_context (enc->oc);
    439     enc->oc = NULL;
    440   }
    441 }
    442 
    443 
    444 /**
    445  * Main method for the opus-preview plugin.
    446  *
    447  * @param ec extraction context
    448  */
    449 void
    450 EXTRACTOR_previewopus_extract_method (struct EXTRACTOR_ExtractContext *ec);
    451 
    452 void
    453 EXTRACTOR_previewopus_extract_method (struct EXTRACTOR_ExtractContext *ec)
    454 {
    455   struct EXTRACTOR_MediaOptions opt;
    456   struct EXTRACTOR_FFmpegIO io;
    457   struct Encoder enc;
    458   AVCodecContext *ctx = NULL;
    459   int16_t *pcm = NULL;
    460   uint8_t *preview = NULL;
    461   size_t preview_size = 0;
    462   size_t max_samples;
    463   size_t have;
    464   size_t limit;
    465   double target;
    466   int stream;
    467 
    468   EXTRACTOR_media_options_parse_ (ec->config,
    469                                   &opt);
    470   EXTRACTOR_media_deadline_start_ (&io.deadline,
    471                                    opt.deadline);
    472   io.ec = ec;
    473   memset (&enc, 0, sizeof (enc));
    474   if (! EXTRACTOR_media_is_media_ (ec, 1))
    475     return;
    476   if (0 != EXTRACTOR_ffmpeg_open_ (&io))
    477     return;
    478   stream = EXTRACTOR_ffmpeg_open_stream_ (&io,
    479                                           AVMEDIA_TYPE_AUDIO,
    480                                           &ctx);
    481   if (0 > stream)
    482     goto cleanup;
    483   target = EXTRACTOR_ffmpeg_seek_ (&io,
    484                                    stream,
    485                                    ctx,
    486                                    opt.offset);
    487   max_samples = (size_t) opt.length * OUTPUT_SAMPLE_RATE;
    488   if (NULL == (pcm = malloc (max_samples * sizeof (int16_t))))
    489     goto cleanup;
    490   have = decode_pcm (&io,
    491                      ctx,
    492                      stream,
    493                      target,
    494                      max_samples,
    495                      pcm);
    496   if (have < (size_t) MIN_PREVIEW_MS * OUTPUT_SAMPLE_RATE / 1000)
    497     goto cleanup;   /* too short to be worth anything */
    498   EXTRACTOR_media_fade_s16_ (pcm,
    499                              have,
    500                              FADE_MS * OUTPUT_SAMPLE_RATE / 1000);
    501   if (0 != encoder_start (&opt,
    502                           &enc))
    503     goto cleanup;
    504   if (0 != encoder_finish (&enc,
    505                            pcm,
    506                            have,
    507                            &preview,
    508                            &preview_size))
    509     goto cleanup;
    510   /* the caller asked for a preview of a given length and bitrate, so let
    511      that decide the limit, but never emit something absurd */
    512   limit = (size_t) opt.length * opt.bitrate / 8 + 8192;
    513   if (limit < EXTRACTOR_MEDIA_MAX_PREVIEW)
    514     limit = EXTRACTOR_MEDIA_MAX_PREVIEW;
    515   if (preview_size > limit)
    516     goto cleanup;
    517   ec->proc (ec->cls,
    518             "previewopus",
    519             EXTRACTOR_METATYPE_AUDIO_PREVIEW,
    520             EXTRACTOR_METAFORMAT_BINARY,
    521             "audio/ogg",
    522             (const char *) preview,
    523             preview_size);
    524 cleanup:
    525   if (NULL != preview)
    526     av_free (preview);
    527   encoder_cleanup (&enc);
    528   free (pcm);
    529   if (NULL != ctx)
    530     avcodec_free_context (&ctx);
    531   EXTRACTOR_ffmpeg_close_ (&io);
    532 }
    533 
    534 
    535 /**
    536  * Keep the diagnostics of the media framework out of the output of the
    537  * calling application; a decoder complaining about a broken file is not
    538  * something the application asked to see.
    539  *
    540  * ("force-kill" would be tempting here, to get a fresh process for every
    541  * file, but the host cannot know about that special without loading the
    542  * plugin in-process: it only finds out that the child is gone once it has
    543  * already handed it the next file, which is then silently lost.)
    544  *
    545  * @return special options for this plugin
    546  */
    547 const char *
    548 EXTRACTOR_previewopus_options (void);
    549 
    550 const char *
    551 EXTRACTOR_previewopus_options ()
    552 {
    553   return "close-stderr";
    554 }
    555 
    556 
    557 /**
    558  * Silence FFmpeg's logging.
    559  */
    560 void __attribute__ ((constructor))
    561 previewopus_init (void);
    562 
    563 void __attribute__ ((constructor))
    564 previewopus_init ()
    565 {
    566   EXTRACTOR_ffmpeg_silence_ ();
    567 }
    568 
    569 
    570 /* end of previewopus_extractor.c */