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 */