[FFmpeg-devel,1/2] lavu: add locale-independent sscanf implementation

Submitted by Paul B Mahol on Nov. 12, 2018, 6:46 p.m.

Details

Message ID 20181112184645.10561-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Nov. 12, 2018, 6:46 p.m.
Copied and adopted from musl implementation.
 * converted all 'long double' to 'double'

Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 libavutil/avstring.c | 961 +++++++++++++++++++++++++++++++++++++++++++
 libavutil/avstring.h |   6 +
 2 files changed, 967 insertions(+)

Comments

Michael Niedermayer Nov. 12, 2018, 7:08 p.m.
On Mon, Nov 12, 2018 at 07:46:45PM +0100, Paul B Mahol wrote:
> Copied and adopted from musl implementation.
>  * converted all 'long double' to 'double'
> 
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  libavutil/avstring.c | 961 +++++++++++++++++++++++++++++++++++++++++++
>  libavutil/avstring.h |   6 +
>  2 files changed, 967 insertions(+)
[...]
> +static int ff_vfscanf(FFFILE *f, const char *fmt, va_list ap)
> +{
> +    int width;
> +    int size;
> +    int base;
> +    const unsigned char *p;
> +    int c, t;
> +    char *s;
> +    void *dest=NULL;
> +    int invert;
> +    int matches=0;
> +    unsigned long long x;
> +    double y;
> +    off_t pos = 0;
> +    unsigned char scanset[257];
> +    size_t i, k;
> +
> +    for (p=(const unsigned char *)fmt; *p; p++) {
> +
> +        if (av_isspace(*p)) {
> +            while (av_isspace(p[1])) p++;
> +            shlim(f, 0);
> +            while (av_isspace(shgetc(f)));
> +            shunget(f);
> +            pos += shcnt(f);
> +            continue;
> +        }
> +        if (*p != '%' || p[1] == '%') {
> +            shlim(f, 0);
> +            if (*p == '%') {
> +                p++;
> +                while (av_isspace((c=shgetc(f))));
> +            } else {
> +                c = shgetc(f);
> +            }
> +            if (c!=*p) {
> +                shunget(f);
> +                if (c<0) goto input_fail;
> +                goto match_fail;
> +            }
> +            pos += shcnt(f);
> +            continue;
> +        }
> +
> +        p++;
> +        if (*p=='*') {
> +            dest = 0; p++;
> +        } else if (av_isdigit(*p) && p[1]=='$') {
> +            dest = arg_n(ap, *p-'0'); p+=2;
> +        } else {
> +            dest = va_arg(ap, void *);
> +        }
> +
> +        for (width=0; av_isdigit(*p); p++) {
> +            width = 10*width + *p - '0';
> +        }
> +
> +        if (*p=='m') {
> +            s = 0;
> +            p++;
> +        }
> +
> +        size = SIZE_def;
> +        switch (*p++) {
> +        case 'h':
> +            if (*p == 'h') p++, size = SIZE_hh;
> +            else size = SIZE_h;
> +            break;
> +        case 'l':
> +            if (*p == 'l') p++, size = SIZE_ll;
> +            else size = SIZE_l;
> +            break;
> +        case 'j':
> +            size = SIZE_ll;
> +            break;
> +        case 'z':
> +        case 't':
> +            size = SIZE_l;
> +            break;
> +        case 'L':
> +            size = SIZE_L;
> +            break;
> +        case 'd': case 'i': case 'o': case 'u': case 'x':
> +        case 'a': case 'e': case 'f': case 'g':
> +        case 'A': case 'E': case 'F': case 'G': case 'X':
> +        case 's': case 'c': case '[':
> +        case 'S': case 'C':
> +        case 'p': case 'n':
> +            p--;
> +            break;
> +        default:
> +            goto fmt_fail;
> +        }
> +
> +        t = *p;
> +
> +        /* C or S */
> +        if ((t&0x2f) == 3) {
> +            t |= 32;
> +            size = SIZE_l;
> +        }
> +
> +        switch (t) {
> +            case 'c':
> +                if (width < 1) width = 1;
> +            case '[':
> +                break;
> +            case 'n':
> +                store_int(dest, size, pos);
> +                /* do not increment match count, etc! */
> +                continue;
> +            default:
> +                shlim(f, 0);
> +                while (av_isspace(shgetc(f)));
> +                shunget(f);
> +                pos += shcnt(f);
> +        }
> +
> +        shlim(f, width);
> +        if (shgetc(f) < 0) goto input_fail;
> +        shunget(f);
> +
> +        switch (t) {
> +            case 's':
> +            case 'c':
> +            case '[':
> +                if (t == 'c' || t == 's') {
> +                    memset(scanset, -1, sizeof scanset);
> +                    scanset[0] = 0;
> +                    if (t == 's') {
> +                        scanset[1+'\t'] = 0;
> +                        scanset[1+'\n'] = 0;
> +                        scanset[1+'\v'] = 0;
> +                        scanset[1+'\f'] = 0;
> +                        scanset[1+'\r'] = 0;
> +                        scanset[1+' '] = 0;
> +                    }
> +                } else {
> +                    if (*++p == '^') p++, invert = 1;
> +                    else invert = 0;
> +                    memset(scanset, invert, sizeof scanset);
> +                    scanset[0] = 0;
> +                    if (*p == '-') p++, scanset[1+'-'] = 1-invert;
> +                    else if (*p == ']') p++, scanset[1+']'] = 1-invert;
> +                    for (; *p != ']'; p++) {
> +                        if (!*p) goto fmt_fail;
> +                        if (*p=='-' && p[1] && p[1] != ']')
> +                            for (c=p++[-1]; c<*p; c++)
> +                                scanset[1+c] = 1-invert;
> +                        scanset[1+*p] = 1-invert;
> +                    }
> +                }
> +                s = 0;
> +                i = 0;

> +                k = t=='c' ? width+1U : 31;

k is set but not used


[...]
James Almer Nov. 12, 2018, 7:08 p.m.
On 11/12/2018 3:46 PM, Paul B Mahol wrote:
> Copied and adopted from musl implementation.
>  * converted all 'long double' to 'double'
> 
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  libavutil/avstring.c | 961 +++++++++++++++++++++++++++++++++++++++++++
>  libavutil/avstring.h |   6 +
>  2 files changed, 967 insertions(+)
> 
> diff --git a/libavutil/avstring.c b/libavutil/avstring.c
> index f03dd25141..8511ea2c16 100644
> --- a/libavutil/avstring.c
> +++ b/libavutil/avstring.c
> @@ -19,6 +19,29 @@
>   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>   */
>  
> +/*
> + * Copyright (c) 2005-2014 Rich Felker, et al.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining
> + * a copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sublicense, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be
> + * included in all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> + */

Since this is a crapload of new code, and this notice only applies to
the new code in question, maybe this should be added as a separate file
instead.
Tomas Härdin Nov. 12, 2018, 8:38 p.m.
mån 2018-11-12 klockan 19:46 +0100 skrev Paul B Mahol:
> Copied and adopted from musl implementation.
>  * converted all 'long double' to 'double'
> 
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  libavutil/avstring.c | 961
> 

Why not set locale instead?

/Tomas
Nicolas George Nov. 12, 2018, 8:41 p.m.
Tomas Härdin (2018-11-12):
> Why not set locale instead?

That would conflict with the wishes of the application. A library should
never change global state.

(Of course, the core of the problem is that locales are global state,
this is braindead design.)

Regards,
Tomas Härdin Nov. 13, 2018, 11:40 a.m.
mån 2018-11-12 klockan 21:41 +0100 skrev Nicolas George:
> Tomas Härdin (2018-11-12):
> > Why not set locale instead?
> 
> That would conflict with the wishes of the application. A library
> should
> never change global state.
> 
> (Of course, the core of the problem is that locales are global state,
> this is braindead design.)

So fork() instead. This is a horrible solution

/Tomas
Paul B Mahol Nov. 13, 2018, 11:47 a.m.
On 11/13/18, Tomas Härdin <tjoppen@acc.umu.se> wrote:
> mån 2018-11-12 klockan 21:41 +0100 skrev Nicolas George:
>> Tomas Härdin (2018-11-12):
>> > Why not set locale instead?
>>
>> That would conflict with the wishes of the application. A library
>> should
>> never change global state.
>>
>> (Of course, the core of the problem is that locales are global state,
>> this is braindead design.)
>
> So fork() instead. This is a horrible solution

Look at rest of functions, we already have av_isspace and etc.
And saying its horrible solution is disgrace to me.
Tomas Härdin Nov. 13, 2018, 4:58 p.m.
tis 2018-11-13 klockan 12:47 +0100 skrev Paul B Mahol:
> On 11/13/18, Tomas Härdin <tjoppen@acc.umu.se> wrote:
> > mån 2018-11-12 klockan 21:41 +0100 skrev Nicolas George:
> > > Tomas Härdin (2018-11-12):
> > > > Why not set locale instead?
> > > 
> > > That would conflict with the wishes of the application. A library
> > > should
> > > never change global state.
> > > 
> > > (Of course, the core of the problem is that locales are global
> > > state,
> > > this is braindead design.)
> > 
> > So fork() instead. This is a horrible solution
> 
> Look at rest of functions, we already have av_isspace and etc.
> And saying its horrible solution is disgrace to me.

That was probably uncalled for, sorry. It just irks me how awful libc
is that one should have to reimplement everything it does..

/Tomas

Patch hide | download patch | download mbox

diff --git a/libavutil/avstring.c b/libavutil/avstring.c
index f03dd25141..8511ea2c16 100644
--- a/libavutil/avstring.c
+++ b/libavutil/avstring.c
@@ -19,6 +19,29 @@ 
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+/*
+ * Copyright (c) 2005-2014 Rich Felker, et al.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
 #include <stdarg.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -30,6 +53,7 @@ 
 #include "avassert.h"
 #include "avstring.h"
 #include "bprint.h"
+#include "float.h"
 
 int av_strstart(const char *str, const char *pfx, const char **ptr)
 {
@@ -458,3 +482,940 @@  int av_match_list(const char *name, const char *list, char separator)
 
     return 0;
 }
+
+typedef struct FFFILE {
+    size_t buf_size;
+    unsigned char *buf;
+    unsigned char *rpos, *rend;
+    unsigned char *shend;
+    off_t shlim, shcnt;
+    void *cookie;
+    size_t (*read)(struct FFFILE *, unsigned char *, size_t);
+} FFFILE;
+
+#define SIZE_hh -2
+#define SIZE_h  -1
+#define SIZE_def 0
+#define SIZE_l   1
+#define SIZE_L   2
+#define SIZE_ll  3
+
+#define shcnt(f) ((f)->shcnt + ((f)->rpos - (f)->buf))
+
+static int fftoread(FFFILE *f)
+{
+    f->rpos = f->rend = f->buf + f->buf_size;
+    return 0;
+}
+
+static size_t ffstring_read(FFFILE *f, unsigned char *buf, size_t len)
+{
+    char *src = f->cookie;
+    size_t k = len+256;
+    char *end = memchr(src, 0, k);
+
+    if (end) k = end-src;
+    if (k < len) len = k;
+    memcpy(buf, src, len);
+    f->rpos = (void *)(src+len);
+    f->rend = (void *)(src+k);
+    f->cookie = src+k;
+
+    return len;
+}
+
+static int ffuflow(FFFILE *f)
+{
+    unsigned char c;
+    if (!fftoread(f) && f->read(f, &c, 1)==1) return c;
+    return EOF;
+}
+
+static void ffshlim(FFFILE *f, off_t lim)
+{
+    f->shlim = lim;
+    f->shcnt = f->buf - f->rpos;
+    /* If lim is nonzero, rend must be a valid pointer. */
+    if (lim && f->rend - f->rpos > lim)
+        f->shend = f->rpos + lim;
+    else
+        f->shend = f->rend;
+}
+
+static int ffshgetc(FFFILE *f)
+{
+    int c;
+    off_t cnt = shcnt(f);
+    if (f->shlim && cnt >= f->shlim || (c=ffuflow(f)) < 0) {
+        f->shcnt = f->buf - f->rpos + cnt;
+        f->shend = 0;
+        return EOF;
+    }
+    cnt++;
+    if (f->shlim && f->rend - f->rpos > f->shlim - cnt)
+        f->shend = f->rpos + (f->shlim - cnt);
+    else
+        f->shend = f->rend;
+    f->shcnt = f->buf - f->rpos + cnt;
+    if (f->rpos[-1] != c) f->rpos[-1] = c;
+    return c;
+}
+
+#define shlim(f, lim) ffshlim((f), (lim))
+#define shgetc(f) (((f)->rpos != (f)->shend) ? *(f)->rpos++ : ffshgetc(f))
+#define shunget(f) ((f)->shend ? (void)(f)->rpos-- : (void)0)
+
+static const unsigned char table[] = { -1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1,
+    -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
+    25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
+    -1,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,
+    25,26,27,28,29,30,31,32,33,34,35,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+    -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
+};
+
+static unsigned long long ffintscan(FFFILE *f, unsigned base, int pok, unsigned long long lim)
+{
+    const unsigned char *val = table+1;
+    int c, neg=0;
+    unsigned x;
+    unsigned long long y;
+    if (base > 36 || base == 1) {
+        errno = EINVAL;
+        return 0;
+    }
+    while (av_isspace((c=shgetc(f))));
+    if (c=='+' || c=='-') {
+        neg = -(c=='-');
+        c = shgetc(f);
+    }
+    if ((base == 0 || base == 16) && c=='0') {
+        c = shgetc(f);
+        if ((c|32)=='x') {
+            c = shgetc(f);
+            if (val[c]>=16) {
+                shunget(f);
+                if (pok) shunget(f);
+                else shlim(f, 0);
+                return 0;
+            }
+            base = 16;
+        } else if (base == 0) {
+            base = 8;
+        }
+    } else {
+        if (base == 0) base = 10;
+        if (val[c] >= base) {
+            shunget(f);
+            shlim(f, 0);
+            errno = EINVAL;
+            return 0;
+        }
+    }
+    if (base == 10) {
+        for (x=0; c-'0'<10U && x<=UINT_MAX/10-1; c=shgetc(f))
+            x = x*10 + (c-'0');
+        for (y=x; c-'0'<10U && y<=ULLONG_MAX/10 && 10*y<=ULLONG_MAX-(c-'0'); c=shgetc(f))
+            y = y*10 + (c-'0');
+        if (c-'0'>=10U) goto done;
+    } else if (!(base & base-1)) {
+        int bs = "\0\1\2\4\7\3\6\5"[(0x17*base)>>5&7];
+        for (x=0; val[c]<base && x<=UINT_MAX/32; c=shgetc(f))
+            x = x<<bs | val[c];
+        for (y=x; val[c]<base && y<=ULLONG_MAX>>bs; c=shgetc(f))
+            y = y<<bs | val[c];
+    } else {
+        for (x=0; val[c]<base && x<=UINT_MAX/36-1; c=shgetc(f))
+            x = x*base + val[c];
+        for (y=x; val[c]<base && y<=ULLONG_MAX/base && base*y<=ULLONG_MAX-val[c]; c=shgetc(f))
+            y = y*base + val[c];
+    }
+    if (val[c]<base) {
+        for (; val[c]<base; c=shgetc(f));
+        errno = ERANGE;
+        y = lim;
+        if (lim&1) neg = 0;
+    }
+done:
+    shunget(f);
+    if (y>=lim) {
+        if (!(lim&1) && !neg) {
+            errno = ERANGE;
+            return lim-1;
+        } else if (y>lim) {
+            errno = ERANGE;
+            return lim;
+        }
+    }
+    return (y^neg)-neg;
+}
+
+static long long scanexp(FFFILE *f, int pok)
+{
+    int c;
+    int x;
+    long long y;
+    int neg = 0;
+
+    c = shgetc(f);
+    if (c=='+' || c=='-') {
+        neg = (c=='-');
+        c = shgetc(f);
+        if (c-'0'>=10U && pok) shunget(f);
+    }
+    if (c-'0'>=10U) {
+        shunget(f);
+        return LLONG_MIN;
+    }
+    for (x=0; c-'0'<10U && x<INT_MAX/10; c = shgetc(f))
+        x = 10*x + c-'0';
+    for (y=x; c-'0'<10U && y<LLONG_MAX/100; c = shgetc(f))
+        y = 10*y + c-'0';
+    for (; c-'0'<10U; c = shgetc(f));
+    shunget(f);
+    return neg ? -y : y;
+}
+
+#define LD_B1B_DIG 2
+#define LD_B1B_MAX 9007199, 254740991
+#define KMAX 128
+#define MASK (KMAX-1)
+#define CONCAT2(x,y) x ## y
+#define CONCAT(x,y) CONCAT2(x,y)
+
+static double decfloat(FFFILE *f, int c, int bits, int emin, int sign, int pok)
+{
+    uint32_t x[KMAX];
+    static const uint32_t th[] = { LD_B1B_MAX };
+    int i, j, k, a, z;
+    long long lrp=0, dc=0;
+    long long e10=0;
+    int lnz = 0;
+    int gotdig = 0, gotrad = 0;
+    int rp;
+    int e2;
+    int emax = -emin-bits+3;
+    int denormal = 0;
+    double y;
+    double frac=0;
+    double bias=0;
+    static const int p10s[] = { 10, 100, 1000, 10000,
+        100000, 1000000, 10000000, 100000000 };
+
+    j=0;
+    k=0;
+
+    /* Don't let leading zeros consume buffer space */
+    for (; c=='0'; c = shgetc(f)) gotdig=1;
+    if (c=='.') {
+        gotrad = 1;
+        for (c = shgetc(f); c=='0'; c = shgetc(f)) gotdig=1, lrp--;
+    }
+
+    x[0] = 0;
+    for (; c-'0'<10U || c=='.'; c = shgetc(f)) {
+        if (c == '.') {
+            if (gotrad) break;
+            gotrad = 1;
+            lrp = dc;
+        } else if (k < KMAX-3) {
+            dc++;
+            if (c!='0') lnz = dc;
+            if (j) x[k] = x[k]*10 + c-'0';
+            else x[k] = c-'0';
+            if (++j==9) {
+                k++;
+                j=0;
+            }
+            gotdig=1;
+        } else {
+            dc++;
+            if (c!='0') {
+                lnz = (KMAX-4)*9;
+                x[KMAX-4] |= 1;
+            }
+        }
+    }
+    if (!gotrad) lrp=dc;
+
+    if (gotdig && (c|32)=='e') {
+        e10 = scanexp(f, pok);
+        if (e10 == LLONG_MIN) {
+            if (pok) {
+                shunget(f);
+            } else {
+                shlim(f, 0);
+                return 0;
+            }
+            e10 = 0;
+        }
+        lrp += e10;
+    } else if (c>=0) {
+        shunget(f);
+    }
+    if (!gotdig) {
+        errno = EINVAL;
+        shlim(f, 0);
+        return 0;
+    }
+
+    /* Handle zero specially to avoid nasty special cases later */
+    if (!x[0]) return sign * 0.0;
+
+    /* Optimize small integers (w/no exponent) and over/under-flow */
+    if (lrp==dc && dc<10 && (bits>30 || x[0]>>bits==0))
+        return sign * (double)x[0];
+    if (lrp > -emin/2) {
+        errno = ERANGE;
+        return sign * DBL_MAX * DBL_MAX;
+    }
+    if (lrp < emin-2*DBL_MANT_DIG) {
+        errno = ERANGE;
+        return sign * DBL_MIN * DBL_MIN;
+    }
+
+    /* Align incomplete final B1B digit */
+    if (j) {
+        for (; j<9; j++) x[k]*=10;
+        k++;
+        j=0;
+    }
+
+    a = 0;
+    z = k;
+    e2 = 0;
+    rp = lrp;
+
+    /* Optimize small to mid-size integers (even in exp. notation) */
+    if (lnz<9 && lnz<=rp && rp < 18) {
+        if (rp == 9) return sign * (double)x[0];
+        if (rp < 9) return sign * (double)x[0] / p10s[8-rp];
+        int bitlim = bits-3*(int)(rp-9);
+        if (bitlim>30 || x[0]>>bitlim==0)
+            return sign * (double)x[0] * p10s[rp-10];
+    }
+
+    /* Drop trailing zeros */
+    for (; !x[z-1]; z--);
+
+    /* Align radix point to B1B digit boundary */
+    if (rp % 9) {
+        int rpm9 = rp>=0 ? rp%9 : rp%9+9;
+        int p10 = p10s[8-rpm9];
+        uint32_t carry = 0;
+        for (k=a; k!=z; k++) {
+            uint32_t tmp = x[k] % p10;
+            x[k] = x[k]/p10 + carry;
+            carry = 1000000000/p10 * tmp;
+            if (k==a && !x[k]) {
+                a = (a+1 & MASK);
+                rp -= 9;
+            }
+        }
+        if (carry) x[z++] = carry;
+        rp += 9-rpm9;
+    }
+
+    /* Upscale until desired number of bits are left of radix point */
+    while (rp < 9*LD_B1B_DIG || (rp == 9*LD_B1B_DIG && x[a]<th[0])) {
+        uint32_t carry = 0;
+        e2 -= 29;
+        for (k=(z-1 & MASK); ; k=(k-1 & MASK)) {
+            uint64_t tmp = ((uint64_t)x[k] << 29) + carry;
+            if (tmp > 1000000000) {
+                carry = tmp / 1000000000;
+                x[k] = tmp % 1000000000;
+            } else {
+                carry = 0;
+                x[k] = tmp;
+            }
+            if (k==(z-1 & MASK) && k!=a && !x[k]) z = k;
+            if (k==a) break;
+        }
+        if (carry) {
+            rp += 9;
+            a = (a-1 & MASK);
+            if (a == z) {
+                z = (z-1 & MASK);
+                x[z-1 & MASK] |= x[z];
+            }
+            x[a] = carry;
+        }
+    }
+
+    /* Downscale until exactly number of bits are left of radix point */
+    for (;;) {
+        uint32_t carry = 0;
+        int sh = 1;
+        for (i=0; i<LD_B1B_DIG; i++) {
+            k = (a+i & MASK);
+            if (k == z || x[k] < th[i]) {
+                i=LD_B1B_DIG;
+                break;
+            }
+            if (x[a+i & MASK] > th[i]) break;
+        }
+        if (i==LD_B1B_DIG && rp==9*LD_B1B_DIG) break;
+        /* FIXME: find a way to compute optimal sh */
+        if (rp > 9+9*LD_B1B_DIG) sh = 9;
+        e2 += sh;
+        for (k=a; k!=z; k=(k+1 & MASK)) {
+            uint32_t tmp = x[k] & (1<<sh)-1;
+            x[k] = (x[k]>>sh) + carry;
+            carry = (1000000000>>sh) * tmp;
+            if (k==a && !x[k]) {
+                a = (a+1 & MASK);
+                i--;
+                rp -= 9;
+            }
+        }
+        if (carry) {
+            if ((z+1 & MASK) != a) {
+                x[z] = carry;
+                z = (z+1 & MASK);
+            } else x[z-1 & MASK] |= 1;
+        }
+    }
+
+    /* Assemble desired bits into floating point variable */
+    for (y=i=0; i<LD_B1B_DIG; i++) {
+        if ((a+i & MASK)==z) x[(z=(z+1 & MASK))-1] = 0;
+        y = 1000000000.0L * y + x[a+i & MASK];
+    }
+
+    y *= sign;
+
+    /* Limit precision for denormal results */
+    if (bits > DBL_MANT_DIG+e2-emin) {
+        bits = DBL_MANT_DIG+e2-emin;
+        if (bits<0) bits=0;
+        denormal = 1;
+    }
+
+    /* Calculate bias term to force rounding, move out lower bits */
+    if (bits < DBL_MANT_DIG) {
+        bias = copysignl(scalbn(1, 2*DBL_MANT_DIG-bits-1), y);
+        frac = fmodl(y, scalbn(1, DBL_MANT_DIG-bits));
+        y -= frac;
+        y += bias;
+    }
+
+    /* Process tail of decimal input so it can affect rounding */
+    if ((a+i & MASK) != z) {
+        uint32_t t = x[a+i & MASK];
+        if (t < 500000000 && (t || (a+i+1 & MASK) != z))
+            frac += 0.25*sign;
+        else if (t > 500000000)
+            frac += 0.75*sign;
+        else if (t == 500000000) {
+            if ((a+i+1 & MASK) == z)
+                frac += 0.5*sign;
+            else
+                frac += 0.75*sign;
+        }
+        if (DBL_MANT_DIG-bits >= 2 && !fmodl(frac, 1))
+            frac++;
+    }
+
+    y += frac;
+    y -= bias;
+
+    if ((e2+DBL_MANT_DIG & INT_MAX) > emax-5) {
+        if (fabs(y) >= CONCAT(0x1p, DBL_MANT_DIG)) {
+            if (denormal && bits==DBL_MANT_DIG+e2-emin)
+                denormal = 0;
+            y *= 0.5;
+            e2++;
+        }
+        if (e2+DBL_MANT_DIG>emax || (denormal && frac))
+            errno = ERANGE;
+    }
+
+    return scalbnl(y, e2);
+}
+
+static double hexfloat(FFFILE *f, int bits, int emin, int sign, int pok)
+{
+    uint32_t x = 0;
+    double y = 0;
+    double scale = 1;
+    double bias = 0;
+    int gottail = 0, gotrad = 0, gotdig = 0;
+    long long rp = 0;
+    long long dc = 0;
+    long long e2 = 0;
+    int d;
+    int c;
+
+    c = shgetc(f);
+
+    /* Skip leading zeros */
+    for (; c=='0'; c = shgetc(f))
+        gotdig = 1;
+
+    if (c=='.') {
+        gotrad = 1;
+        c = shgetc(f);
+        /* Count zeros after the radix point before significand */
+        for (rp=0; c=='0'; c = shgetc(f), rp--) gotdig = 1;
+    }
+
+    for (; c-'0'<10U || (c|32)-'a'<6U || c=='.'; c = shgetc(f)) {
+        if (c=='.') {
+            if (gotrad) break;
+            rp = dc;
+            gotrad = 1;
+        } else {
+            gotdig = 1;
+            if (c > '9') d = (c|32)+10-'a';
+            else d = c-'0';
+            if (dc<8) {
+                x = x*16 + d;
+            } else if (dc < DBL_MANT_DIG/4+1) {
+                y += d*(scale/=16);
+            } else if (d && !gottail) {
+                y += 0.5*scale;
+                gottail = 1;
+            }
+            dc++;
+        }
+    }
+    if (!gotdig) {
+        shunget(f);
+        if (pok) {
+            shunget(f);
+            if (gotrad) shunget(f);
+        } else {
+            shlim(f, 0);
+        }
+        return sign * 0.0;
+    }
+    if (!gotrad) rp = dc;
+    while (dc<8) x *= 16, dc++;
+    if ((c|32)=='p') {
+        e2 = scanexp(f, pok);
+        if (e2 == LLONG_MIN) {
+            if (pok) {
+                shunget(f);
+            } else {
+                shlim(f, 0);
+                return 0;
+            }
+            e2 = 0;
+        }
+    } else {
+        shunget(f);
+    }
+    e2 += 4*rp - 32;
+
+    if (!x) return sign * 0.0;
+    if (e2 > -emin) {
+        errno = ERANGE;
+        return sign * DBL_MAX * DBL_MAX;
+    }
+    if (e2 < emin-2*DBL_MANT_DIG) {
+        errno = ERANGE;
+        return sign * DBL_MIN * DBL_MIN;
+    }
+
+    while (x < 0x80000000) {
+        if (y>=0.5) {
+            x += x + 1;
+            y += y - 1;
+        } else {
+            x += x;
+            y += y;
+        }
+        e2--;
+    }
+
+    if (bits > 32+e2-emin) {
+        bits = 32+e2-emin;
+        if (bits<0) bits=0;
+    }
+
+    if (bits < DBL_MANT_DIG)
+        bias = copysignl(scalbn(1, 32+DBL_MANT_DIG-bits-1), sign);
+
+    if (bits<32 && y && !(x&1)) x++, y=0;
+
+    y = bias + sign*(double)x + sign*y;
+    y -= bias;
+
+    if (!y) errno = ERANGE;
+
+    return scalbnl(y, e2);
+}
+
+static double fffloatscan(FFFILE *f, int prec, int pok)
+{
+    int sign = 1;
+    size_t i;
+    int bits;
+    int emin;
+    int c;
+
+    switch (prec) {
+    case 0:
+        bits = FLT_MANT_DIG;
+        emin = FLT_MIN_EXP-bits;
+        break;
+    case 1:
+        bits = DBL_MANT_DIG;
+        emin = DBL_MIN_EXP-bits;
+        break;
+    case 2:
+        bits = DBL_MANT_DIG;
+        emin = DBL_MIN_EXP-bits;
+        break;
+    default:
+        return 0;
+    }
+
+    while (av_isspace((c = shgetc(f))));
+
+    if (c=='+' || c=='-') {
+        sign -= 2*(c=='-');
+        c = shgetc(f);
+    }
+
+    for (i=0; i<8 && (c|32)=="infinity"[i]; i++)
+        if (i<7) c = shgetc(f);
+    if (i==3 || i==8 || (i>3 && pok)) {
+        if (i!=8) {
+            shunget(f);
+            if (pok) for (; i>3; i--) shunget(f);
+        }
+        return sign * INFINITY;
+    }
+    if (!i) for (i=0; i<3 && (c|32)=="nan"[i]; i++)
+        if (i<2) c = shgetc(f);
+    if (i==3) {
+        if (shgetc(f) != '(') {
+            shunget(f);
+            return NAN;
+        }
+        for (i=1; ; i++) {
+            c = shgetc(f);
+            if (c-'0'<10U || c-'A'<26U || c-'a'<26U || c=='_')
+                continue;
+            if (c==')') return NAN;
+            shunget(f);
+            if (!pok) {
+                errno = EINVAL;
+                shlim(f, 0);
+                return 0;
+            }
+            while (i--) shunget(f);
+            return NAN;
+        }
+        return NAN;
+    }
+
+    if (i) {
+        shunget(f);
+        errno = EINVAL;
+        shlim(f, 0);
+        return 0;
+    }
+
+    if (c=='0') {
+        c = shgetc(f);
+        if ((c|32) == 'x')
+            return hexfloat(f, bits, emin, sign, pok);
+        shunget(f);
+        c = '0';
+    }
+
+    return decfloat(f, c, bits, emin, sign, pok);
+}
+
+static void *arg_n(va_list ap, unsigned int n)
+{
+    void *p;
+    unsigned int i;
+    va_list ap2;
+    va_copy(ap2, ap);
+    for (i=n; i>1; i--) va_arg(ap2, void *);
+    p = va_arg(ap2, void *);
+    va_end(ap2);
+    return p;
+}
+
+static void store_int(void *dest, int size, unsigned long long i)
+{
+    if (!dest) return;
+    switch (size) {
+    case SIZE_hh:
+        *(char *)dest = i;
+        break;
+    case SIZE_h:
+        *(short *)dest = i;
+        break;
+    case SIZE_def:
+        *(int *)dest = i;
+        break;
+    case SIZE_l:
+        *(long *)dest = i;
+        break;
+    case SIZE_ll:
+        *(long long *)dest = i;
+        break;
+    }
+}
+
+static int ff_vfscanf(FFFILE *f, const char *fmt, va_list ap)
+{
+    int width;
+    int size;
+    int base;
+    const unsigned char *p;
+    int c, t;
+    char *s;
+    void *dest=NULL;
+    int invert;
+    int matches=0;
+    unsigned long long x;
+    double y;
+    off_t pos = 0;
+    unsigned char scanset[257];
+    size_t i, k;
+
+    for (p=(const unsigned char *)fmt; *p; p++) {
+
+        if (av_isspace(*p)) {
+            while (av_isspace(p[1])) p++;
+            shlim(f, 0);
+            while (av_isspace(shgetc(f)));
+            shunget(f);
+            pos += shcnt(f);
+            continue;
+        }
+        if (*p != '%' || p[1] == '%') {
+            shlim(f, 0);
+            if (*p == '%') {
+                p++;
+                while (av_isspace((c=shgetc(f))));
+            } else {
+                c = shgetc(f);
+            }
+            if (c!=*p) {
+                shunget(f);
+                if (c<0) goto input_fail;
+                goto match_fail;
+            }
+            pos += shcnt(f);
+            continue;
+        }
+
+        p++;
+        if (*p=='*') {
+            dest = 0; p++;
+        } else if (av_isdigit(*p) && p[1]=='$') {
+            dest = arg_n(ap, *p-'0'); p+=2;
+        } else {
+            dest = va_arg(ap, void *);
+        }
+
+        for (width=0; av_isdigit(*p); p++) {
+            width = 10*width + *p - '0';
+        }
+
+        if (*p=='m') {
+            s = 0;
+            p++;
+        }
+
+        size = SIZE_def;
+        switch (*p++) {
+        case 'h':
+            if (*p == 'h') p++, size = SIZE_hh;
+            else size = SIZE_h;
+            break;
+        case 'l':
+            if (*p == 'l') p++, size = SIZE_ll;
+            else size = SIZE_l;
+            break;
+        case 'j':
+            size = SIZE_ll;
+            break;
+        case 'z':
+        case 't':
+            size = SIZE_l;
+            break;
+        case 'L':
+            size = SIZE_L;
+            break;
+        case 'd': case 'i': case 'o': case 'u': case 'x':
+        case 'a': case 'e': case 'f': case 'g':
+        case 'A': case 'E': case 'F': case 'G': case 'X':
+        case 's': case 'c': case '[':
+        case 'S': case 'C':
+        case 'p': case 'n':
+            p--;
+            break;
+        default:
+            goto fmt_fail;
+        }
+
+        t = *p;
+
+        /* C or S */
+        if ((t&0x2f) == 3) {
+            t |= 32;
+            size = SIZE_l;
+        }
+
+        switch (t) {
+            case 'c':
+                if (width < 1) width = 1;
+            case '[':
+                break;
+            case 'n':
+                store_int(dest, size, pos);
+                /* do not increment match count, etc! */
+                continue;
+            default:
+                shlim(f, 0);
+                while (av_isspace(shgetc(f)));
+                shunget(f);
+                pos += shcnt(f);
+        }
+
+        shlim(f, width);
+        if (shgetc(f) < 0) goto input_fail;
+        shunget(f);
+
+        switch (t) {
+            case 's':
+            case 'c':
+            case '[':
+                if (t == 'c' || t == 's') {
+                    memset(scanset, -1, sizeof scanset);
+                    scanset[0] = 0;
+                    if (t == 's') {
+                        scanset[1+'\t'] = 0;
+                        scanset[1+'\n'] = 0;
+                        scanset[1+'\v'] = 0;
+                        scanset[1+'\f'] = 0;
+                        scanset[1+'\r'] = 0;
+                        scanset[1+' '] = 0;
+                    }
+                } else {
+                    if (*++p == '^') p++, invert = 1;
+                    else invert = 0;
+                    memset(scanset, invert, sizeof scanset);
+                    scanset[0] = 0;
+                    if (*p == '-') p++, scanset[1+'-'] = 1-invert;
+                    else if (*p == ']') p++, scanset[1+']'] = 1-invert;
+                    for (; *p != ']'; p++) {
+                        if (!*p) goto fmt_fail;
+                        if (*p=='-' && p[1] && p[1] != ']')
+                            for (c=p++[-1]; c<*p; c++)
+                                scanset[1+c] = 1-invert;
+                        scanset[1+*p] = 1-invert;
+                    }
+                }
+                s = 0;
+                i = 0;
+                k = t=='c' ? width+1U : 31;
+                if ((s = dest)) {
+                    while (scanset[(c=shgetc(f))+1])
+                        s[i++] = c;
+                } else {
+                    while (scanset[(c=shgetc(f))+1]);
+                }
+                shunget(f);
+                if (!shcnt(f)) goto match_fail;
+                if (t == 'c' && shcnt(f) != width) goto match_fail;
+                if (t != 'c') {
+                    if (s) s[i] = 0;
+                }
+                break;
+            case 'p':
+            case 'X':
+            case 'x':
+                base = 16;
+                goto int_common;
+            case 'o':
+                base = 8;
+                goto int_common;
+            case 'd':
+            case 'u':
+                base = 10;
+                goto int_common;
+            case 'i':
+                base = 0;
+int_common:
+                x = ffintscan(f, base, 0, ULLONG_MAX);
+                if (!shcnt(f))
+                    goto match_fail;
+                if (t=='p' && dest)
+                    *(void **)dest = (void *)(uintptr_t)x;
+                else
+                    store_int(dest, size, x);
+                break;
+            case 'a': case 'A':
+            case 'e': case 'E':
+            case 'f': case 'F':
+            case 'g': case 'G':
+                y = fffloatscan(f, size, 0);
+                if (!shcnt(f))
+                    goto match_fail;
+                if (dest) {
+                    switch (size) {
+                    case SIZE_def:
+                        *(float *)dest = y;
+                        break;
+                    case SIZE_l:
+                        *(double *)dest = y;
+                        break;
+                    case SIZE_L:
+                        *(double *)dest = y;
+                        break;
+                    }
+                }
+                break;
+        }
+
+        pos += shcnt(f);
+        if (dest) matches++;
+    }
+    if (0) {
+fmt_fail:
+input_fail:
+        if (!matches) matches--;
+    }
+match_fail:
+    return matches;
+}
+
+static int ff_vsscanf(const char *s, const char *fmt, va_list ap)
+{
+    FFFILE f = {
+        .buf = (void *)s, .cookie = (void *)s,
+        .read = ffstring_read,
+    };
+
+    return ff_vfscanf(&f, fmt, ap);
+}
+
+int av_sscanf(const char *string, const char *format, ...)
+{
+    int ret;
+    va_list ap;
+    va_start(ap, format);
+    ret = ff_vsscanf(string, format, ap);
+    va_end(ap);
+    return ret;
+}
diff --git a/libavutil/avstring.h b/libavutil/avstring.h
index 04d2695640..37dd4e2da0 100644
--- a/libavutil/avstring.h
+++ b/libavutil/avstring.h
@@ -400,6 +400,12 @@  int av_utf8_decode(int32_t *codep, const uint8_t **bufp, const uint8_t *buf_end,
  */
 int av_match_list(const char *name, const char *list, char separator);
 
+/**
+ * See libc sscanf manual for more information.
+ * Locale-independent sscanf implementation.
+ */
+int av_sscanf(const char *string, const char *format, ...);
+
 /**
  * @}
  */