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 CB7144DF88 for ; Thu, 8 Jan 2026 23:35:50 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'rQkxD09m/1NRt/zDNb0ynUja7QS1PjLOAnGnfuHJmI0=', expected b'OYJoz4/kHcPR8OyImFGhI7OLnbDJ8vqrQomkh/j+K1M=')) 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=1767915321; 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=v+fWUDwO0fDJjRxa908cSlhZTatO5lvoHGrg6824wTM=; b=L8InxO89/BAwOFht0SdvMZ9BGLtuQ8KoSixWzV0ChzmLdjRjTq8FqD8BiznYPke/p7eN4 zq8DmJDs6OKIWlfQ/DZe7yuk2ykTFxmH2HfUdPre8/Rb1ZZHYjaTQmGv8C+UTuPSZ7ZXRj8 on27VAS1NLAcUArbIpXXZqc2dmj9KddMwaZLw0Z4ANimORJ77fIzfj8BBNx/m9LpD7aUaZg GT7Sv/KJtJSRjaOG6ahwD91CSPZg0qWxcq8wo6XyH+cJQxBRocLCTnubbf9Whd2fdIqZYvY 3R+AMHCi6+lqpCxtKJTJjuDRLottgkewgbNVJABjI+MUcdFqzj502HJzMYxA== Received: from [172.20.0.4] (unknown [172.20.0.4]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 39385690DCE; Fri, 9 Jan 2026 01:35:21 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1767915302; b=G4TAzDapnugKRmmMZhX24QUdcNoKt4qsrmzMezZPk2KriJYfaAw1rEHfnH46JdlJh/jnr xIF74wDsfiJ2ZHnwIIhYtt6yC6jy7A1536NX15ei4e3yy1oqahmWXQgkRUWlxhvAXgCBYkq 0wD9a354gnHgD3Olzy5SEP+g/vLPDhstIe341LmwepQTXdhoLpAPxrF3HY+KO0ma9EqcucS 9ztCaR/J5xLBQPl3axXRWdwgVxyZndKj5NHQR27zBXvGEZ5ZAS/NYDMC0hOO8UU3wZImpba ZlMSdMK02qBte2tnTYbbph1X4iv4OYH56AHPi35dG1V/8Cbdtj2kQqGx9J0w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1767915302; 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=rQkxD09m/1NRt/zDNb0ynUja7QS1PjLOAnGnfuHJmI0=; b=jL3dFHukGlpRyQsfk7aWSQvEozIlN5ERBdKGtaAIeHdnP4RnAT/LaA/isJmwHrl2QJ9fG NSQRm7vS71issPWJO60VrHz/rZJPA2gnlDvgzv5KLoFhwmVtVJU/bwBdWx4F4I2ohzHgjRr c4ZOY07YJfHEygQADHKGAPVyPGx5Bd6fLD3Pp0Y5rGtt+ZEBD44u24CZU1dLdYUoeHoqm0Y 7CWY5nrQpbmLtNv6DAIwbuBEk9/QL4OJHHsI22sUc5qvNZ+njw3W4fVROJxCvDVSOBh9rhA gXg45AUWFC7qYGL1nVl62Q/5AH2dlI6ahBOZIUxTSX+3t5F11wt9ak3sxPfQ== 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-f182.google.com (mail-vk1-f182.google.com [209.85.221.182]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 7E2F8690D32 for ; Fri, 9 Jan 2026 01:34:48 +0200 (EET) Received: by mail-vk1-f182.google.com with SMTP id 71dfb90a1353d-55b26332196so1183441e0c.3 for ; Thu, 08 Jan 2026 15:34:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1767915286; x=1768520086; 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=OYJoz4/kHcPR8OyImFGhI7OLnbDJ8vqrQomkh/j+K1M=; b=cnuc82plvJ4RXZ2iGrwNTEfVeL0WQDFYt8ZewkCl0tAzaAOikkeJbblIdfZ5PZwef+ HsDsghAvhPJrAMrmdIhXBy0CGAAkgyz22G4VFMv4BEmCG15QL4cuSS2s9Ku/IB1OauMz yQQTb3IRetvwFDCNv7wMW+R7XY4z5oZudCMAV30MfBt8qO+tdbvHstrETiPKm6+SWvGs GUpF0SMPzRCi7MIVNAHt+EvQHcalC1RaVHJmVBsmDxzddt67kdBfjFH9nFZg69qWPRXj Y625j3hGbXYI7na9C5SWQCQ/TeFB68JmMwLIB1PQYhVDgbxakfYdkDRWmE3hIm3C266S Ilmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1767915286; x=1768520086; 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=OYJoz4/kHcPR8OyImFGhI7OLnbDJ8vqrQomkh/j+K1M=; b=oPbw7AjNzk9wVwe9EsG4Fuu8dqtcd8MwKMZYyBhSjJI8QY0MuixcgLzIvWo1hklChd PZF9SE8GVv2cYAOTZcj4/3YeEfWjFCIXTIFkgNCIMVwauAYpZxVcKo4dEFUZyAi0n458 bynkf8nd9hEf/RfCkT3TI90I0gMOqwTv5Ah5/zMkDS+ACA/N5mVW6VZn38EFnNNHl+zA VkbqX7B8/pG/B1APlBFXaeXhFwWRAlqvlXfIWPX5Tv9ZoYp7dq4itTQk75rkiNYig+oK FWhIUQ5N9pGlK8RxHZtUuOloWvmQaGey9W950Bc7DbdsEQvo7zoypRYMO8aHWGqYEHGe KeDw== X-Gm-Message-State: AOJu0YwLWaQO42ep7DVlwMM/04VvxyTY+6w/JmT7HSNebHsCtAepYDUI 4V9E7/h2OtwIucdjTkS7HPX5QbeRfHE5WF+K+jSxCbELW5+NuqdL7n/lxAWyDngE X-Gm-Gg: AY/fxX453ylJ+OR/K8W9hMYCWjh5q+Y1VbOhnq8HW0EFRJq+waFJCNt1p/u7LS6LBOB rQ074rvehsr2uBI9wi4hYusu7igYSmZ4gPIWmTF+Tnx7DSIDQKAULTPPhyXDiBm0h0Gp9+oUdJA x05rYFgSx4Rrkjx29LhwtEe1Wahc8m23Y4s0J4mnvNuSUhysvxdUOz1Y9fU7YQmNV4cmZUQ/E1t TOU+SWqFMaD77ivrINXSnF2UdaElUsfuV2vas1pHfGakWHWsMTZgCrFnUrGlIoyORAbjODeVE81 gyM5sKb9Q0DiUNQdeTVtBSvebYWBCnmIvB7rN+p+kWR+ENI+D4XutxEl3Dj0nEh7OfXiLp6aIWw Ko4XGYWCoQXC/QVfCHVa17VRHMwLC50/91qQydp7jYDe5nw0HSCW3bvsh27dLcd6yNBCM3koK2K w5y3VyAt0/vpdZhvdOKZnC X-Google-Smtp-Source: AGHT+IGD2gCOQwqrNd6LlqaafqGg/EL+V7sc05Q0kmjLVTRslOFlW0b9R2Ukcv6YnJ863XaVdIQ2Pw== X-Received: by 2002:a05:6122:421b:b0:559:85d5:bfbd with SMTP id 71dfb90a1353d-56347d65a34mr2375095e0c.9.1767915286163; Thu, 08 Jan 2026 15:34:46 -0800 (PST) Received: from victor-MS-7C94 ([179.126.166.45]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5633a2076bbsm7609272e0c.6.2026.01.08.15.34.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 08 Jan 2026 15:34:45 -0800 (PST) To: ffmpeg-devel@ffmpeg.org Date: Thu, 8 Jan 2026 20:34:30 -0300 Message-ID: <20260108233430.38783-1-victormeloasm@gmail.com> X-Mailer: git-send-email 2.51.0 MIME-Version: 1.0 Message-ID-Hash: WOA5D24P5WCZ4E4XUO5ID3326UISXR7Q X-Message-ID-Hash: WOA5D24P5WCZ4E4XUO5ID3326UISXR7Q X-MailFrom: SRS0=STxT=7N=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: reduce branches in encoder fast path 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: --- 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