العودة إلى المدونة
13 أغسطس 2026Sergei Solod9 دقائق قراءة

كيف جعلت فيديوهات H.264 أصغر بعدة مرات من دون تراجع واضح في الجودة

ظل الترميز H.264 نفسه، لكنني أعدت بناء سياسة الترميز حول CRF 28 وx264 veryslow وحد أقصى بدقة 720p ومعدل إطارات مفيد فقط. النتيجة: ملفات أصغر بعدة مرات مع بقاء التشغيل العادي طبيعيًا، والأهم أنني بدأت أتعامل مع الترميز كتكلفة تُدفع مرة واحدة ومع الباندويث كتكلفة تتكرر مع كل مشاهدة.

H.264FFmpegضغط الفيديوأداء الويبx264

بدت النتيجة كنوع التحسن الذي أتوقعه عادةً بعد الانتقال إلى codec أحدث: أصبحت ملفات الفيديو لدي أصغر بعدة مرات، بينما ظل التشغيل العادي يبدو طبيعيًا ولم ألاحظ تراجعًا واضحًا في الجودة عند أحجام المشاهدة المعتادة.

لكنني لم أنتقل إلى AV1 أو HEVC أو VP9. كنت ما زلت أستخدم H.264 داخل MP4.

الذي تغير هو كل ما يحيط بالـ codec. أعدت بناء سياسة الترميز لتناسب workload محددًا جدًا: مقاطع قصيرة مرسومة ومتحركة، جمهور يغلب عليه استخدام الهاتف، الباندويث هو التكلفة المتكررة الأهم، ولا يهمني تقريبًا كم يستغرق الترميز غير الفوري الذي يحدث مرة واحدة.

الخط الأساسي الذي استقررت عليه محافظ عمدًا من ناحية التشغيل ومكلف عمدًا من ناحية الترميز: H.264 Main Profile @ Level 3.1، وavc1، و8-bit yuv420p، وCRF 28، وx264 veryslow، وحد أقصى من فئة 720p، ومعدل إطارات مفيد لا يتجاوز عادةً 30 fps، وحدود واضحة للـ reference frames وB-frames، وfaststart لتسليم MP4 تدريجيًا.

أهم تحسين لم يكن خيارًا واحدًا في FFmpeg

أكبر تغيير كان في طريقة تفكيري في التكلفة.

الترميز يحدث مرة واحدة. أما نقل الملف فيحدث كل مرة يطلبه فيها أحد.

في الفيديو الفوري قد يكون إنفاق قدر أكبر بكثير من CPU لتوفير قليل من bitrate صفقة سيئة. ملفاتي تُرمّز offline ثم تُخدم مرارًا. في هذا النموذج قد يصبح توفير عشر دقائق وقت الترميز بلا قيمة تقريبًا إذا جعل الترميز الأسرع كل طلب مستقبلي أكبر.

لهذا يناسبني -preset veryslow. أنا مستعد لدفع تكلفة CPU مرة واحدة إذا كان x264 سيستخدمها للعثور على تمثيل أكثر كفاءة. المتصفح لا يكرر بحث الـ encoder؛ هو فقط يفك ترميز bitstream النهائي.

أصبحت القاعدة بسيطة: أنفق الحساب في الخطوة التي تحدث مرة واحدة، وكن بخيلًا بالبايتات في الخطوة التي تتكرر.

لماذا بقيت مع H.264 بدل مطاردة أحدث codec

أنا لا أقول إن H.264 هو الأكثر كفاءة في الضغط. ليس كذلك. يمكن أن تكون codecs الأحدث جذابة عندما يستطيع نظام التسليم الاحتفاظ بعدة renditions واختيار الأنسب لكل جهاز.

قيدي كان مختلفًا: URL واحد، ملف واحد، codec واحد، وأقل قدر عملي من مشاكل التشغيل لدى جمهور يغلب عليه الهاتف.

لهذا الغرض يبقى H.264 داخل MP4 baseline آمنًا جدًا. توصي Apple حاليًا مطوري الويب باستخدام ملفات MP4 مرمّزة بـ H.264 للفيديو الثابت في Safari. وتذكر وثائق Android الحالية H.264 داخل MP4 وتلزم بوجود decoder لـ Main Profile منذ Android 6.0 وما بعده؛ كما تتضمن توصيات التشغيل لديها 1280×720 عند 30 fps كإعداد H.264 بدقة HD. راجع تنسيقات الوسائط المدعومة في Android.

هذا لا يعني أن الأجهزة الحديثة محدودة بـ Main Profile أو Level 3.1. إرشادات Apple لـ HLS، مثلًا، تفضل عمومًا High Profile على Main أو Baseline. اخترت Main@3.1 لأنني أريد متطلبات decoder متواضعة عمدًا لملف MP4 ثابت واحد، لا لأن Apple تفرضه.

توقفت عن ترميز pixels لا تحتاج إلى الوجود

كانت الدقة واحدة من أكبر الروافع. أصبح السقف لدي تقريبًا 1280×720 للوضع الأفقي، و720×1280 للعمودي، ونحو 960×960 للمحتوى المربع أو المختلط.

القاعدة الأهم: لا تقم بالـ upscale لمجرد الوصول إلى السقف.

إذا كان المصدر 900×600، فإن تحويله إلى 1280×720 لا يعيد أي تفاصيل مفقودة. هو فقط ينشئ عينات أكثر على الـ encoder أن يصفها. يمكن خفض مصدر 1920×1080 إلى فئة 720p، بينما يمكن أن يبقى مصدر 900×600 قريبًا من 900×600. السقف حد أقصى، وليس هدفًا يجب الوصول إليه.

هذا يبدو بسيطًا، لكن إزالة pixels غير الضرورية قد تكون أهم من كثير من تعديلات encoder الغامضة.

توقفت عن الدفع مقابل frames لا يملكها المصدر فعليًا

معدل الإطارات multiplier آخر. إذا كانت animation تحتوي تقريبًا على 16 حالة بصرية مفيدة في الثانية، فإن تخزينها على 30 أو 60 fps لا يصنع حركة أفضل تلقائيًا. غالبًا ما يصنع عينات زمنية مكررة أو مولدة، ومع ذلك يجب تمثيلها.

سياستي هي الحفاظ على الإيقاع المفيد للمصدر والبقاء عادةً عند 30 fps أو أقل. لهذا النوع من المحتوى يمكن أن تكون 12 أو 15 أو 16 أو 18 أو 20 أو 24 أو 25 أو 30 fps كلها منطقية عندما تصف المصدر فعلًا.

أفضل أيضًا CFR نظيفًا في الناتج المُنشأ. VFR ليس معطوبًا بطبيعته؛ لكن CFR يجعل timestamps وعدد الإطارات والتحقق من المدة وseeking والتحقق اللاحق أسهل في pipeline لدي.

المبدأ العام أهم من أي رقم FPS واحد: لا تدفع باندويث مقابل معلومات زمنية غير موجودة في المصدر.

CRF 28 اختيار للـ workload وليس رقمًا سحريًا

لم أرد إجبار كل مقطع نحو target bitrate واحد. الرسم شبه الثابت والمشهد ذو الحركة المعقدة لا يحتاجان العدد نفسه من البتات ليبدوا مقبولين.

لذلك أستخدم وضع CRF في x264، ولهذا الـ workload المرسوم الذي يعطي الأولوية للباندويث استقررت حول -crf 28. توثق FFmpeg الـ CRF في libx264 باعتباره rate control للجودة الثابتة؛ راجع وثائق codecs في FFmpeg.

CRF 28 عدواني عمدًا. لن أنسخه بشكل أعمى إلى film grain أو تصوير كاميرا مليء بالضجيج أو نص صغير على الشاشة أو workload تهم فيه fidelity أكثر من الباندويث.

ولا أملك score إدراكيًا عالميًا يثبت أن CRF 28 شفاف بصريًا. ما يمكنني قوله عن workload الخاص بي أضيق من ذلك: الملفات الناتجة أصبحت أصغر بصورة كبيرة وظلت تبدو طبيعية لي أثناء التشغيل العادي. هذه ملاحظة عملية، وليست ادعاءً بأن CRF 28 visually lossless.

veryslow مكلف للـ encoder، وليس بالضرورة للـ decoder

الـ preset لدي هو -preset veryslow. الـ preset الأبطأ يمنح x264 فرصة أكبر للبحث عن قرارات prediction وcoding أكثر كفاءة. الثمن هو CPU ووقت الترميز.

التمييز المهم هو أن مجهود الـ encoder وتعقيد الـ decoder ليسا الشيء نفسه.

أستطيع أن أجعل x264 يعمل بقوة ثم أضع قيودًا مستقلة على stream النهائي. عقد الخرج المحافظ لدي هو:

H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled

تعرض FFmpeg الـ CRF والـ presets والـ tuning وقيود profile والـ reference frames والـ B-frames بصورة منفصلة. وهذا بالضبط ما أفعله ذهنيًا: دع الـ encoder يبحث بعمق، لكن أبقِ جانب التشغيل عاديًا.

GOP وVBV حواجز أمان، وليسا أداة الجودة الأساسية

لهذه المقاطع القصيرة التي تُشغّل progressive أستخدم GOP أقصى يقارب خمس ثوانٍ: نحو -g 150 عند 30 fps، و-g 120 عند 24 fps، أو -g 80 عند 16 fps.

هذا اختيار خاص بالـ workload، وليس قاعدة عامة. Adaptive streaming له قيود مختلفة؛ فإرشادات Apple لتأليف HLS، مثلًا، توصي بـ IDR كل ثانيتين. أنا لا أنسخ قاعدة HLS تلك بصورة عمياء إلى ملفات MP4 ثابتة قصيرة تُشغّل progressive.

أستخدم كذلك تقريبًا:

-maxrate:v 4M
-bufsize:v 8M

هذه القيم سقف ضد القفزات غير المعتادة في bitrate. وهي لا تعني «رمّز كل شيء على 4 Mbps». يظل CRF مسؤولًا عن التوزيع الطبيعي للبتات، ولذلك تبقى المقاطع السهلة حرة في أن تصبح صغيرة جدًا.

جعلت حاوية MP4 مملة أيضًا

أستخدم avc1 صراحةً. توصي وثائق Apple الحالية لـ HLS بصيغ sample مثل avc1 بدل avc3. هذا ليس سبب صغر ملفاتي، لكنه يتوافق مع هدفي في إنتاج H.264 داخل MP4 بشكل تقليدي.

وأستخدم أيضًا -movflags +faststart. تقول وثائق formats في FFmpeg إن faststart ينقل index الخاص بـ moov إلى بداية MP4. كما تشترط متطلبات Android للبث عبر HTTP أن يأتي moov قبل mdat بعد ftyp في MPEG-4.

ftyp
moov
mdat

faststart لا يحسن الضغط. هو يجعل التشغيل التدريجي عبر HTTP أقل إزعاجًا.

لخرج SDR عادي أستخدم 8-bit yuv420p وأشير إلى BT.709 مع limited/video range. إذا كان المقطع بلا صوت فلا أصنع audio track من لا شيء. ولهذا المحتوى المرسوم أستخدم أيضًا -tune animation؛ وأتعامل معه كاختيار خاص بنوع المحتوى، لا كجزء من عقد التوافق العام.

بروفايل FFmpeg الأساسي

لمصدر مرسوم بسرعة 30 fps، يبدو الجزء المركزي من الأمر تقريبًا هكذا:

ffmpeg -i input \
  -c:v libx264 \
  -preset veryslow \
  -tune animation \
  -crf 28 \
  -profile:v main \
  -level:v 3.1 \
  -pix_fmt yuv420p \
  -tag:v avc1 \
  -refs 4 \
  -bf 5 \
  -g 150 \
  -maxrate:v 4M \
  -bufsize:v 8M \
  -x264-params "open-gop=0:b-pyramid=normal:nal-hrd=none" \
  -color_range tv \
  -color_primaries bt709 \
  -color_trc bt709 \
  -colorspace bt709 \
  -an \
  -movflags +faststart \
  output.mp4

تعمدت ألا أثبت مراحل scaling وframe rate داخل هذا المثال. لا يجب تكبير مصدر 900×600 فقط لأن السقف 1280×720، ولا يجب إجبار animation منخفضة المعدل طبيعيًا على 30 fps فقط لأن المثال يستخدم -g 150.

الأمر تطبيق للسياسة، وليس هو السياسة نفسها.

لماذا أصبحت الملفات أصغر بعدة مرات

لم تكن هناك magic flag.

جاء الانخفاض من جمع عدة قرارات، كل واحد منها أزال نوعًا مختلفًا من الهدر: pixels غير ضرورية، frames غير ضرورية، التفكير بعقلية bitrate ثابت، إعدادات encoder رخيصة، keyframes أكثر مما أحتاج، وstreams لا أحتاجها.

لهذا فإن قول «هذا الملف H.264» يخبرك بالقليل المدهش عن حجمه. يمكن لترميزين H.264 للمصدر نفسه أن يختلفا بشكل كبير لأن اسم الـ codec لا يصف الدقة ولا frame rate ولا rate control ولا preset ولا بنية GOP ولا profile ولا تجهيز المصدر.

في حالتي، تغيير هذه القرارات المحيطة كان أهم من تغيير الـ codec.

ما الذي لا تثبته النتيجة

لم أعزل كل إعداد في تجربة محكومة، لذلك لا أستطيع بصدق إسناد نسبة دقيقة من التوفير إلى veryslow أو CRF 28 أو خفض الدقة أو خفض frame rate كل على حدة.

كما لا أستطيع الادعاء بأن كل خرج CRF 28 شفاف إدراكيًا. «لا يوجد تراجع واضح في الجودة» هو ما لاحظته لهذا الـ workload المرسوم عند أحجام المشاهدة العادية، وليس ضمانًا علميًا لأي فيديو.

ولا أقول إن ملف H.264 واحد هو المعمارية الصحيحة لكل موقع. تعدد renditions وadaptive streaming وHDR و4K والتفاوض على codec تغير المعادلة.

النتيجة أضيق وأكثر فائدة: لمكتبة من المقاطع القصيرة المرسومة والمتحركة، حيث الباندويث أولوية، والجمهور mobile-heavy، ووقت الترميز رخيص والتشغيل المتوقع مهم، جعل هذا البروفايل ملفاتي أصغر بعدة مرات مع بقائها طبيعية في التشغيل المعتاد.

القاعدة التي أستخدمها الآن

كنت أفكر في تحسين الفيديو أساسًا كمشكلة إعدادات encoder. الآن أفكر فيه كمشكلة تكلفة على عمر الملف.

قد يعمل الـ encoder مرة واحدة. وقد تعبر البايتات الشبكة آلاف أو ملايين المرات.

وهذا يغير معنى كلمة «مكلف».

لا أمانع دفع CPU مرة واحدة. ما أرفضه أكثر هو إرسال pixels صنعها upscale، أو frames لا تضيف حركة مفيدة، أو bitrate لا يحتاجه المحتوى في كل طلب مستقبلي.

بقي الـ codec مملًا: H.264 داخل MP4. التحسين حدث حوله.

لهذا الـ workload، الدرس أوضح من أي flag منفردة في FFmpeg: حسّن التكلفة التي تدفعها مرارًا، لا التكلفة التي تدفعها مرة واحدة.