diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h
index d4b0c3cee2..92f6105443 100644
--- a/libswscale/swscale_internal.h
+++ b/libswscale/swscale_internal.h
@@ -698,6 +698,7 @@ void ff_updateMMXDitherTables(SwsContext *c, int dstY);
 
 av_cold void ff_sws_init_range_convert(SwsContext *c);
 av_cold void ff_sws_init_range_convert_loongarch(SwsContext *c);
+av_cold void ff_sws_init_range_convert_x86(SwsContext *c);
 
 SwsFunc ff_yuv2rgb_init_x86(SwsContext *c);
 SwsFunc ff_yuv2rgb_init_ppc(SwsContext *c);
diff --git a/libswscale/utils.c b/libswscale/utils.c
index 476a24fea5..8dfa57b5ff 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -1082,6 +1082,8 @@ int sws_setColorspaceDetails(struct SwsContext *c, const int inv_table[4],
         ff_sws_init_range_convert(c);
 #if ARCH_LOONGARCH64
         ff_sws_init_range_convert_loongarch(c);
+#elif ARCH_X86
+        ff_sws_init_range_convert_x86(c);
 #endif
     }
 
diff --git a/libswscale/x86/Makefile b/libswscale/x86/Makefile
index 68391494be..f00154941d 100644
--- a/libswscale/x86/Makefile
+++ b/libswscale/x86/Makefile
@@ -12,6 +12,7 @@ X86ASM-OBJS                     += x86/input.o                          \
                                    x86/output.o                         \
                                    x86/scale.o                          \
                                    x86/scale_avx2.o                          \
+                                   x86/range_convert.o                  \
                                    x86/rgb_2_rgb.o                      \
                                    x86/yuv_2_rgb.o                      \
                                    x86/yuv2yuvX.o                       \
diff --git a/libswscale/x86/range_convert.asm b/libswscale/x86/range_convert.asm
new file mode 100644
index 0000000000..333265fb65
--- /dev/null
+++ b/libswscale/x86/range_convert.asm
@@ -0,0 +1,100 @@
+;******************************************************************************
+;* Copyright (c) 2024 Ramiro Polla
+;*
+;* This file is part of FFmpeg.
+;*
+;* FFmpeg is free software; you can redistribute it and/or
+;* modify it under the terms of the GNU Lesser General Public
+;* License as published by the Free Software Foundation; either
+;* version 2.1 of the License, or (at your option) any later version.
+;*
+;* FFmpeg is distributed in the hope that it will be useful,
+;* but WITHOUT ANY WARRANTY; without even the implied warranty of
+;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+;* Lesser General Public License for more details.
+;*
+;* You should have received a copy of the GNU Lesser General Public
+;* License along with FFmpeg; if not, write to the Free Software
+;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+;******************************************************************************
+
+%include "libavutil/x86/x86util.asm"
+
+; NOTE: there is no need to clamp the input when converting to jpeg range
+;       (like we do in the C code) because packssdw will saturate the output.
+
+;-----------------------------------------------------------------------------
+; lumConvertRange
+;
+; void ff_lumRangeToJpeg_<opt>(int16_t *dst, int width);
+; void ff_lumRangeFromJpeg_<opt>(int16_t *dst, int width);
+;
+;-----------------------------------------------------------------------------
+
+%macro LUMCONVERTRANGE 4
+SECTION_RODATA
+mult_%1:        times 4 dd %2
+offset_%1:      times 4 dd %3
+SECTION .text
+cglobal %1, 2, 3, 3, dst, width, x
+    movsxdifnidn widthq, widthd
+    xor              xq, xq
+    mova             m1, [mult_%1]
+    mova             m2, [offset_%1]
+.loop:
+    pmovsxwd         m0, [dstq+xq*2]
+    pmulld           m0, m1
+    paddd            m0, m2
+    psrad            m0, %4
+    packssdw         m0, m0
+    movh    [dstq+xq*2], m0
+    add              xq, mmsize / 4
+    cmp              xd, widthd
+    jl .loop
+    RET
+%endmacro
+
+;-----------------------------------------------------------------------------
+; chrConvertRange
+;
+; void ff_chrRangeToJpeg_<opt>(int16_t *dstU, int16_t *dstV, int width);
+; void ff_chrRangeFromJpeg_<opt>(int16_t *dstU, int16_t *dstV, int width);
+;
+;-----------------------------------------------------------------------------
+
+%macro CHRCONVERTRANGE 4
+SECTION_RODATA
+mult_%1:        times 4 dd %2
+offset_%1:      times 4 dd %3
+SECTION .text
+cglobal %1, 3, 4, 4, dstU, dstV, width, x
+    movsxdifnidn widthq, widthd
+    xor              xq, xq
+    mova             m1, [mult_%1]
+    mova             m2, [offset_%1]
+.loop:
+    pmovsxwd         m0, [dstUq+xq*2]
+    pmulld           m0, m1
+    paddd            m0, m2
+    psrad            m0, %4
+    packssdw         m0, m0
+    movh   [dstUq+xq*2], m0
+    pmovsxwd         m0, [dstVq+xq*2]
+    pmulld           m0, m1
+    paddd            m0, m2
+    psrad            m0, %4
+    packssdw         m0, m0
+    movh   [dstVq+xq*2], m0
+    add              xq, mmsize / 4
+    cmp              xd, widthd
+    jl .loop
+    RET
+%endmacro
+
+%if ARCH_X86_64
+INIT_XMM sse4
+LUMCONVERTRANGE lumRangeToJpeg,   19077, -39057361, 14
+CHRCONVERTRANGE chrRangeToJpeg,    4663,  -9289992, 12
+LUMCONVERTRANGE lumRangeFromJpeg, 14071,  33561947, 14
+CHRCONVERTRANGE chrRangeFromJpeg,  1799,   4081085, 11
+%endif
diff --git a/libswscale/x86/swscale.c b/libswscale/x86/swscale.c
index fff8bb4396..c5ddfb5605 100644
--- a/libswscale/x86/swscale.c
+++ b/libswscale/x86/swscale.c
@@ -447,6 +447,38 @@ INPUT_PLANAR_RGB_UV_ALL_DECL(avx2);
 INPUT_PLANAR_RGB_A_ALL_DECL(avx2);
 #endif
 
+#if ARCH_X86_64
+#define RANGE_CONVERT_FUNCS(opt) do {                                       \
+    if (c->dstBpc <= 14) {                                                  \
+        if (c->srcRange) {                                                  \
+            c->lumConvertRange = ff_lumRangeFromJpeg_ ##opt;                \
+            c->chrConvertRange = ff_chrRangeFromJpeg_ ##opt;                \
+        } else {                                                            \
+            c->lumConvertRange = ff_lumRangeToJpeg_ ##opt;                  \
+            c->chrConvertRange = ff_chrRangeToJpeg_ ##opt;                  \
+        }                                                                   \
+    }                                                                       \
+} while (0)
+
+#define RANGE_CONVERT_FUNCS_DECL(opt)                                       \
+void ff_lumRangeFromJpeg_ ##opt(int16_t *dst, int width);                   \
+void ff_chrRangeFromJpeg_ ##opt(int16_t *dstU, int16_t *dstV, int width);   \
+void ff_lumRangeToJpeg_ ##opt(int16_t *dst, int width);                     \
+void ff_chrRangeToJpeg_ ##opt(int16_t *dstU, int16_t *dstV, int width);     \
+
+RANGE_CONVERT_FUNCS_DECL(sse4);
+
+av_cold void ff_sws_init_range_convert_x86(SwsContext *c)
+{
+    if (c->srcRange != c->dstRange && !isAnyRGB(c->dstFormat)) {
+        int cpu_flags = av_get_cpu_flags();
+        if (EXTERNAL_SSE4(cpu_flags)) {
+            RANGE_CONVERT_FUNCS(sse4);
+        }
+    }
+}
+#endif
+
 av_cold void ff_sws_init_swscale_x86(SwsContext *c)
 {
     int cpu_flags = av_get_cpu_flags();
@@ -805,4 +837,8 @@ switch(c->dstBpc){ \
     }
 
 #endif
+
+#if ARCH_X86_64
+    ff_sws_init_range_convert_x86(c);
+#endif
 }
