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