From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id CFDA24F303 for ; Sun, 22 Feb 2026 07:00:49 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'qst/sGyc/9rCpUAMWH5NzYZTbterifFfZmIdpCvzTnY=', expected b'pwVksKhD1Xn/QlZAM1Wy0tgGU9JdRhxKBtKMqSTXfy8=')) header.d=gmail.com header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1771702507; h=to : date : message-id : mime-version : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=F3DpxYR/4RoejcvJ0nvGO92nxlnjtf+S3LSM9B/nwhs=; b=WWMt8hIeYGMckgJavtCCkPQRU0/SSTKwn6kEJUq2MmMccLfVJDJBhARqJErxKsjXz/w/Q sEN2r2zdnIW0MmsU+/bXLmKLBkPldrcz9/NoJBG2oXfcDZrTmQRGgcjtgzlJHSiQlnFsH9d RuYiqJXblzIWv5UBPJiLagNZhIuZU98q/Ih0y7WDqxUSzt0Qw+7gmE0knkeneyurp5Zx/ap zJ7Ag5TetQnHVKLG0gAa2kOFqJvEY9TzxxGqYy1fxG4fZ1EWrCaGi1wscGywiHBesDJ81fZ GA/va7gSB/B8rbyNvweaG04QIRExpxaYr7z8/3troF9LNYfcQ5NanCQ40wOg== Received: from [172.18.0.3] (unknown [172.18.0.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 7070169125B; Sat, 21 Feb 2026 21:35:07 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1771702494; b=o22PiM4G4LarnXSLJ+uup470TRtZFy4heVS7jjDFwzkQV4f2yP3kI05gZvLtPQLMEhcRY kXaw/3bj07A4aTBGtsVpVMC9DPDEZhbqgTMeZdzChOqR7Y4RC/N0QfwBKXYpmw7Hifu8xyF JLGeAiplTHEZYtNjvBgZn1yyenVMceNjvv9xVGyR/wkCpqceBVcXXLxASL3Imc/VB1bOK5Z Cl1F3+JkVpLikv4hJZYoeYvAFRDIxvHC3wHlNdMDN7aiRVh7cPuOgYq81IMELkcHGWu2gXi IOBJzfIP1dv63fTKKkQv4gcMDAUQ4DWBj2RkXnsZ+fbJpZpolf/kMq0/e17w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1771702494; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=qst/sGyc/9rCpUAMWH5NzYZTbterifFfZmIdpCvzTnY=; b=ICHvgDV5axdw00eL3yWMsqnMGxWxdcjeab3dkfHNwTJeOZGn5Jn2R4xugZllzPSBgMaM6 Bu6ILsw90su19j81Q+hZ6FkKM43W8vjmtlYwJVgX5Ww36hKX2iNXEy4tBI2K7f5nFMugzXi 2M7KLqpWCngqZ5BHlxhwiUakSQEL95xb2OAsUTWG1jal1Y7pa51eg+BSP6HAzBE2ujpMIt8 VmF6SjEgGfORmX90tkwaPcF+MCsJ5m2MDOARMhURSu/iTGXFlrFPM5ayKQO5Mc2h1HeQYK7 hscWn97CdGo9TNXK88lZyXcrMUjiN307ufYIeQbEyi0jkdNDVi6niSn304aw== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=gmail.com; arc=none; dmarc=pass header.from=gmail.com policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=gmail.com; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=gmail.com policy.dmarc=quarantine Received: from mail-vk1-f180.google.com (mail-vk1-f180.google.com [209.85.221.180]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id E6860690E76 for ; Sat, 21 Feb 2026 21:34:38 +0200 (EET) Received: by mail-vk1-f180.google.com with SMTP id 71dfb90a1353d-5673804da95so1262811e0c.0 for ; Sat, 21 Feb 2026 11:34:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771702477; x=1772307277; darn=ffmpeg.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=pwVksKhD1Xn/QlZAM1Wy0tgGU9JdRhxKBtKMqSTXfy8=; b=AJmSKWfz1zHAmSuS5RGjsigezURfAWuzaLXT/kTsjxT1rLOuCgs8URPJ1en/69/iiu 4pqtl2j75nOZD+bNwporjCWIqfZBczJRGSgXRF2HEo7+OQVAbGF9fnHNDpbgrG9hu0FV cVt1I6XWdbEFDsMA/2sWooRFVcY7QzhlHcqWGE9MMHkDmram2HzNbksPEYOofURc8H4e TIYQLJ6jHjaqbpvLYqcWNLxj5RD/NKCnWQOzB9VYYWQVGage4Ods9rzgUYvWaJmTEdZt 98BHSkAQFSaA+NY64qANL6tA+LMCyzioqqdYRvETTiZao2NFOk8WoclWwh6uAIIdFRQI RMyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771702477; x=1772307277; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=pwVksKhD1Xn/QlZAM1Wy0tgGU9JdRhxKBtKMqSTXfy8=; b=f0SD7czhuqLrVR99H1w/CsjAYfVoGE5UrIEtPx3vTV7db7IOb1GuAs5KLKNqzCaAQd 80JEqiTFBAeX5m6BWBq2BJdgN0VmUejCusK2eOtGbYBRJ8bZIGi0pBCMiY//V2dwvWx/ hinOBZTfcn8zvqOmqMHEBfWqXKB2J1AKoWVKpSDdfoSCf/EsiNBQwb4AW+qiatThBSD/ 2rHPmnBacObB2fwiGL9KxgbCoVBnOcBuN62HL6V/65ZmqTtuEd0BZYke/FfX+s9MAxca OYpXBhfDnNHDfzJvUkqAPmBn+/2mmHD8Rla7DrbBOT8E8doSxoepSgqp8GooUfzTy7RS v9Aw== X-Gm-Message-State: AOJu0Yx9okqyhEytyg9VzlOO+08+/wi9FGRQCV+Pu0kQpN0vh3/02d1Z B8e54tHYmFlh5cqHfPWyKJOjpqYKNzSYuV1QkC5vUPVKWgOJtdUSAkIKwgu1Gg== X-Gm-Gg: AZuq6aLKxqHZODuqQRQSHAvWqysOWBVlTcGlDvTLfsXpo7WBReiWAJYmTC7DS95V+DZ gNO7wm5D19Ndqoer3Fr0iCEJ//k9az47o/O30sQEU2F69yrQDLURhBbfUYFI1/T6KQ3FYJD44MM B6hoSMoa26KoNqT2Klrz0gfwV3Jp0gPmOZ+TgwmKrK2+P/tKLLJA7w2PdWLFOgLbtH1xFDqGNsB BnWQD3enDn3/cczLGPzvAwRiAqFuffE6bxcqGBlRbcTZQN0QbMwA2wcrnzncaipEtoNnJTkn9zi F4y2kYiivuVHkCYU2V2y6qklFYg3xcSDPu9G8EyzeUpnKpp+nD8hVsjzIu9d/LK2LURELf6mk69 EC9b2PVGDymKi9DOSNyEXtxEgE04r/tMDwgecUFIAx5u5hM7yZxdKD8c1HUgapM845DhsssmWUO TQoTX4sLZDSKfMEiw/SmpN9S5VaJFvB7BZYQqNBNjNxAVb X-Received: by 2002:a05:6122:1aca:b0:566:398d:96b9 with SMTP id 71dfb90a1353d-568e475263dmr1387551e0c.1.1771702476665; Sat, 21 Feb 2026 11:34:36 -0800 (PST) Received: from victor-MS-7C94 ([179.126.161.182]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-94da8b3ef08sm3045810241.10.2026.02.21.11.34.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Feb 2026 11:34:36 -0800 (PST) To: ffmpeg-devel@ffmpeg.org Date: Sat, 21 Feb 2026 16:31:51 -0300 Message-ID: <20260221193151.26634-1-victormeloasm@gmail.com> X-Mailer: git-send-email 2.51.0 MIME-Version: 1.0 Message-ID-Hash: YEDWS56XCXQ3753JNR6Q6QXPEHFB2SB5 X-Message-ID-Hash: YEDWS56XCXQ3753JNR6Q6QXPEHFB2SB5 X-MailFrom: SRS0=QXkr=AZ=gmail.com=victormeloasm@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PATCH] avutil/base64: optimize base64 encoder/decoder List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Victor Duarte Melo via ffmpeg-devel Cc: Victor Duarte Melo Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: Signed-off-by: Victor Duarte Melo --- libavutil/base64.c | 147 ++++++++++++++++++++++++++++++--------------- libavutil/base64.h | 40 +++++++++--- 2 files changed, 131 insertions(+), 56 deletions(-) diff --git a/libavutil/base64.c b/libavutil/base64.c index 69e11e6f5e..6d3c6c0d83 100644 --- a/libavutil/base64.c +++ b/libavutil/base64.c @@ -22,16 +22,40 @@ * @file * @brief Base64 encode/decode * @author Ryan Martell (with lots of Michael) + * + * This is a drop-in compatible implementation of FFmpeg's base64 helpers. + * The decode routine preserves FFmpeg's historical semantics (strict input, + * stops at the first invalid character, supports unpadded input). + * + * Small performance-oriented changes were made to the encoder: + * - The slow "shift loop" tail handling was replaced by a constant-time + * switch on the remaining 1 or 2 bytes, reducing branches and shifts. + * - The main loop now packs 3 bytes into a 24-bit value directly instead of + * reading an overlapping 32-bit word (avoids endian conversions and makes + * the loop easier for compilers to optimize). + * + * The API and output are fully compatible with the original code. */ #include #include +#include #include "base64.h" #include "error.h" #include "intreadwrite.h" -/* ---------------- private code */ +/* ---------------- private code + * + * map2[c] returns: + * - 0..63 : decoded 6-bit value for valid Base64 symbols + * - 0xFE : "stop" symbol (NUL terminator and '=' padding) + * - 0xFF : invalid symbol (produces AVERROR_INVALIDDATA) + * + * The decoder uses: + * - bits & 0x80 to detect "stop/invalid" quickly (both 0xFE and 0xFF have MSB set) + * - bits & 1 to distinguish invalid (0xFF) from stop (0xFE) + */ static const uint8_t map2[256] = { 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, @@ -72,58 +96,72 @@ static const uint8_t map2[256] = }; #define BASE64_DEC_STEP(i) do { \ - bits = map2[in[i]]; \ - if (bits & 0x80) \ - goto out ## i; \ - v = i ? (v << 6) + bits : bits; \ -} while(0) + bits = map2[in[i]]; \ + if (bits & 0x80) \ + goto out ## i; \ + v = (i) ? (v << 6) + bits : bits; \ +} while (0) int av_base64_decode(uint8_t *out, const char *in_str, int out_size) { uint8_t *dst = out; uint8_t *end; - // no sign extension - const uint8_t *in = in_str; + /* Cast to unsigned to avoid sign extension on platforms where char is signed. */ + const uint8_t *in = (const uint8_t *)in_str; unsigned bits = 0xff; unsigned v; + /* Validation-only mode: keep FFmpeg's original behavior. */ if (!out) goto validity_check; end = out + out_size; + + /* + * Fast path: decode complete 4-char blocks while we can safely do a 32-bit store. + * We write 4 bytes and advance by 3 (the 4th written byte is overwritten on the next iteration). + */ while (end - dst > 3) { BASE64_DEC_STEP(0); BASE64_DEC_STEP(1); BASE64_DEC_STEP(2); BASE64_DEC_STEP(3); - // Using AV_WB32 directly confuses compiler + + /* Convert to native-endian so a native write yields correct byte order in memory. */ v = av_be2ne32(v << 8); AV_WN32(dst, v); + dst += 3; - in += 4; + in += 4; } + + /* Tail: decode at most one more block without overrunning the output buffer. */ if (end - dst) { BASE64_DEC_STEP(0); BASE64_DEC_STEP(1); BASE64_DEC_STEP(2); BASE64_DEC_STEP(3); + *dst++ = v >> 16; if (end - dst) *dst++ = v >> 8; if (end - dst) *dst++ = v; + in += 4; } + validity_check: + /* + * Strict validation: keep decoding groups of 4 until we hit the first stop/invalid. + * Using BASE64_DEC_STEP(0) ensures we always jump to out0 and never touch out1/out2/out3 + * (important for the out == NULL validation-only mode). + */ while (1) { - BASE64_DEC_STEP(0); - in++; - BASE64_DEC_STEP(0); - in++; - BASE64_DEC_STEP(0); - in++; - BASE64_DEC_STEP(0); - in++; + BASE64_DEC_STEP(0); in++; + BASE64_DEC_STEP(0); in++; + BASE64_DEC_STEP(0); in++; + BASE64_DEC_STEP(0); in++; } out3: @@ -135,49 +173,64 @@ out2: *dst++ = v >> 4; out1: out0: - return bits & 1 ? AVERROR_INVALIDDATA : out ? dst - out : 0; + /* bits==0xFE => stop (NUL or '=') => success. bits==0xFF => invalid => error. */ + return (bits & 1) ? AVERROR_INVALIDDATA : (out ? (int)(dst - out) : 0); } /***************************************************************************** -* b64_encode: Stolen from VLC's http.c. -* Simplified by Michael. -* Fixed edge cases and made it work from data (vs. strings) by Ryan. -*****************************************************************************/ + * b64_encode: Stolen from VLC's http.c. + * Simplified by Michael. + * Fixed edge cases and made it work from data (vs. strings) by Ryan. + * + * Encoder micro-optimizations: + * - Direct 24-bit packing (3 bytes -> 4 symbols) in the main loop. + * - Branchless tail handling via a small switch for 1 or 2 remaining bytes. + *****************************************************************************/ char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size) { static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char *ret, *dst; - unsigned i_bits = 0; - int i_shift = 0; - int bytes_remaining = in_size; - if (in_size >= UINT_MAX / 4 || - out_size < AV_BASE64_SIZE(in_size)) + if (in_size >= (int)(UINT_MAX / 4) || out_size < AV_BASE64_SIZE(in_size)) return NULL; + ret = dst = out; - while (bytes_remaining > 3) { - i_bits = AV_RB32(in); - in += 3; bytes_remaining -= 3; - *dst++ = b64[ i_bits>>26 ]; - *dst++ = b64[(i_bits>>20) & 0x3F]; - *dst++ = b64[(i_bits>>14) & 0x3F]; - *dst++ = b64[(i_bits>>8 ) & 0x3F]; - } - i_bits = 0; - while (bytes_remaining) { - i_bits = (i_bits << 8) + *in++; - bytes_remaining--; - i_shift += 8; + + /* Encode full 3-byte blocks. */ + while (in_size >= 3) { + uint32_t v = ((uint32_t)in[0] << 16) | + ((uint32_t)in[1] << 8) | + ((uint32_t)in[2] ); + in += 3; + in_size -= 3; + + dst[0] = b64[ (v >> 18) ]; + dst[1] = b64[ (v >> 12) & 0x3F ]; + dst[2] = b64[ (v >> 6) & 0x3F ]; + dst[3] = b64[ (v ) & 0x3F ]; + dst += 4; } - while (i_shift > 0) { - *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f]; - i_shift -= 6; + + /* Encode the remaining 1 or 2 bytes (if any) and add '=' padding. */ + if (in_size == 1) { + uint32_t v = (uint32_t)in[0]; + dst[0] = b64[(v >> 2) & 0x3F]; + dst[1] = b64[(v & 0x03) << 4]; + dst[2] = '='; + dst[3] = '='; + dst += 4; + } else if (in_size == 2) { + uint32_t v = ((uint32_t)in[0] << 8) | (uint32_t)in[1]; + dst[0] = b64[(v >> 10) & 0x3F]; + dst[1] = b64[(v >> 4) & 0x3F]; + dst[2] = b64[(v & 0x0F) << 2]; + dst[3] = '='; + dst += 4; } - while ((dst - ret) & 3) - *dst++ = '='; - *dst = '\0'; + /* NUL-terminate. The caller guaranteed enough space via AV_BASE64_SIZE(). */ + *dst = '\0'; return ret; } diff --git a/libavutil/base64.h b/libavutil/base64.h index 2954c12d42..31bd8357e3 100644 --- a/libavutil/base64.h +++ b/libavutil/base64.h @@ -23,6 +23,16 @@ #include +/* + * NOTE: This header intentionally keeps the original FFmpeg API surface + * (function names, macros and semantics). The implementation shipped in + * base64.c is a drop-in replacement with extra tests/benchmarks around it. + */ + +#ifdef __cplusplus +extern "C" { +#endif + /** * @defgroup lavu_base64 Base64 * @ingroup lavu_crypto @@ -32,12 +42,17 @@ /** * Decode a base64-encoded string. * - * @param out buffer for decoded data + * The input must be a NUL-terminated string. This decoder is strict: + * it does not ignore whitespace and it stops at the first invalid byte + * (including the terminating NUL). This matches FFmpeg's historical behavior. + * + * @param out buffer for decoded data, or NULL to only validate input * @param in null-terminated input string * @param out_size size in bytes of the out buffer, must be at - * least 3/4 of the length of in, that is AV_BASE64_DECODE_SIZE(strlen(in)) - * @return number of bytes written, or a negative value in case of - * invalid input + * least 3/4 of the length of in, that is + * AV_BASE64_DECODE_SIZE(strlen(in)) + * @return number of bytes written, 0 for validation-only success, + * or a negative value in case of invalid input */ int av_base64_decode(uint8_t *out, const char *in, int out_size); @@ -50,12 +65,15 @@ int av_base64_decode(uint8_t *out, const char *in, int out_size); /** * Encode data to base64 and null-terminate. * + * The output is padded using '=' and is always NUL-terminated (if out is large + * enough). This matches FFmpeg's av_base64_encode behavior. + * * @param out buffer for encoded data * @param out_size size in bytes of the out buffer (including the * null terminator), must be at least AV_BASE64_SIZE(in_size) * @param in input buffer containing the data to encode * @param in_size size in bytes of the in buffer - * @return out or NULL in case of error + * @return out or NULL in case of error (e.g. output too small) */ char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); @@ -63,10 +81,14 @@ char *av_base64_encode(char *out, int out_size, const uint8_t *in, int in_size); * Calculate the output size needed to base64-encode x bytes to a * null-terminated string. */ -#define AV_BASE64_SIZE(x) (((x)+2) / 3 * 4 + 1) +#define AV_BASE64_SIZE(x) (((x) + 2) / 3 * 4 + 1) + +/** + * @} + */ - /** - * @} - */ +#ifdef __cplusplus +} /* extern "C" */ +#endif #endif /* AVUTIL_BASE64_H */ -- 2.51.0 _______________________________________________ ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org