Commit 851bdb6208 for ffmpeg
commit 851bdb620827017d0002a9f98a6df3c8b080915d
Author: Niklas Haas <git@haasn.dev>
Date: Mon Oct 5 13:27:07 2026 +0200
avformat/demux: allow returning AVERROR(EAGAIN) from read_probe()
This fills a gap in the probe scoring API; where it's not possible for
a demuxer to both express "I don't know yet" and "this file could definitely
be mine" at the same time.
This is needed for the example of JPEG Multi-Picture Format files, which
are uniquely distinguished from regular JPEG files by the presence of a
special marker that can live arbitrarily far into the file.
This is a bit analogous to the situation with ID3v2 tags, which can also
hide the true file format behind them. In this case, the probe logic already
has a special hack to detect and force more data (cf. `nodat`) - but letting
the demuxer itself decide this case is an IMHO more elegant solution.
Signed-off-by: Niklas Haas <git@haasn.dev>
diff --git a/libavformat/demux.h b/libavformat/demux.h
index 4fdf3fdf27..ed64afd325 100644
--- a/libavformat/demux.h
+++ b/libavformat/demux.h
@@ -88,6 +88,15 @@ typedef struct FFInputFormat {
* Tell if a given file has a chance of being parsed as this format.
* The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes
* big so you do not have to check for that unless you need more.
+ *
+ * If the final score is not yet resolvable, the special return value
+ * AVERROR(EAGAIN) can be used to signal to the caller that probing must
+ * be tried again with more bytes.
+ *
+ * This is distinct from AVPROBE_SCORE_RETRY, which means "possibly mine,
+ * but if another format is more confident, let it have the file", whereas
+ * AVERROR(EAGAIN) means "possibly mine, don't let another format claim
+ * it until I've seen enough data to make a decision".
*/
int (*read_probe)(const AVProbeData *);
diff --git a/libavformat/format.c b/libavformat/format.c
index 45f382c2a1..a412312ebc 100644
--- a/libavformat/format.c
+++ b/libavformat/format.c
@@ -153,8 +153,9 @@ const AVInputFormat *av_find_input_format(const char *short_name)
return NULL;
}
-const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
- int is_opened, int *score_ret)
+static const AVInputFormat *
+probe_input_format_internal(const AVProbeData *pd, int is_opened,
+ int *score_ret, int *undecided)
{
AVProbeData lpd = *pd;
const AVInputFormat *fmt1 = NULL;
@@ -196,7 +197,13 @@ const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
score = 0;
if (ffifmt(fmt1)->read_probe) {
score = ffifmt(fmt1)->read_probe(&lpd);
- if (score)
+ if (score == AVERROR(EAGAIN)) {
+ av_log(NULL, AV_LOG_TRACE, "Probing %s score:undecided size:%d\n",
+ fmt1->name, lpd.buf_size);
+ if (undecided)
+ *undecided = 1;
+ score = 0;
+ } else if (score)
av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
switch (nodat) {
@@ -235,11 +242,18 @@ const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
return fmt;
}
+const AVInputFormat *av_probe_input_format3(const AVProbeData *pd,
+ int is_opened, int *score_ret)
+{
+ return probe_input_format_internal(pd, is_opened, score_ret, NULL);
+}
+
const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
int is_opened, int *score_max)
{
int score_ret;
- const AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
+ const AVInputFormat *fmt;
+ fmt = probe_input_format_internal(pd, is_opened, &score_ret, NULL);
if (score_ret > *score_max) {
*score_max = score_ret;
return fmt;
@@ -250,7 +264,7 @@ const AVInputFormat *av_probe_input_format2(const AVProbeData *pd,
const AVInputFormat *av_probe_input_format(const AVProbeData *pd, int is_opened)
{
int score = 0;
- return av_probe_input_format2(pd, is_opened, &score);
+ return probe_input_format_internal(pd, is_opened, &score, NULL);
}
int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,
@@ -313,7 +327,18 @@ int av_probe_input_buffer2(AVIOContext *pb, const AVInputFormat **fmt,
memset(pd.buf + pd.buf_size, 0, AVPROBE_PADDING_SIZE);
/* Guess file format. */
- *fmt = av_probe_input_format2(&pd, 1, &score);
+ int score_new, undecided = 0;
+ const AVInputFormat *fmt_new;
+ fmt_new = probe_input_format_internal(&pd, 1, &score_new, &undecided);
+
+ /* Retry undecided formats as long as we have more data to feed */
+ if (undecided && probe_size < max_probe_size && !eof) {
+ continue;
+ } else if (score_new > score) {
+ score = score_new;
+ *fmt = fmt_new;
+ }
+
if (*fmt) {
/* This can only be true in the last iteration. */
if (score <= AVPROBE_SCORE_RETRY) {