ফলাফলটি এমন ছিল, যা আমি সাধারণত নতুন কোনো codec-এ গেলে আশা করতাম: আমার video files কয়েক গুণ ছোট হয়ে গেল, কিন্তু সাধারণ playback এখনও স্বাভাবিক দেখাচ্ছিল এবং স্বাভাবিক viewing size-এ চোখে পড়ার মতো quality drop দেখিনি।
কিন্তু আমি AV1, HEVC বা VP9-এ যাইনি। এখনও MP4-এর ভিতরে H.264 ব্যবহার করছিলাম।
বদলেছিল codec-এর চারপাশের সবকিছু। আমি নির্দিষ্ট একটি workload-এর জন্য encoding policy নতুন করে বানিয়েছিলাম: ছোট illustrated ও animated clip, mobile-heavy audience, বারবার দিতে হয় এমন প্রধান খরচ bandwidth, আর একবারের offline encode কত সময় নেয় সেটা প্রায় গুরুত্বহীন।
শেষ পর্যন্ত যে baseline-এ পৌঁছেছি, সেটি playback-এর জন্য ইচ্ছাকৃতভাবে conservative এবং encoding-এর জন্য ইচ্ছাকৃতভাবে expensive: H.264 Main Profile @ Level 3.1, avc1, 8-bit yuv420p, CRF 28, x264 veryslow, 720p-class resolution ceiling, সাধারণত 30 fps-এর বেশি নয় এমন useful frame rate, সীমিত reference ও B-frame complexity, এবং progressive MP4 delivery-এর জন্য faststart।
সবচেয়ে গুরুত্বপূর্ণ optimization কোনো FFmpeg flag ছিল না
সবচেয়ে বড় পরিবর্তন ছিল cost নিয়ে আমার ভাবনা।
Encoding একবার হয়। Delivery হয় প্রতিবার কেউ file request করলে।
Real-time video-তে সামান্য bitrate বাঁচাতে অনেক বেশি CPU খরচ করা খারাপ trade হতে পারে। আমার files offline encode হয়, তারপর বারবার serve করা হয়। এই model-এ encoding-এর দশ মিনিট বাঁচানো অর্থনৈতিকভাবে তেমন গুরুত্বপূর্ণ নয়, যদি দ্রুত encode-এর কারণে ভবিষ্যতের প্রতিটি request বড় হয়।
এই কারণেই -preset veryslow আমার জন্য অর্থপূর্ণ। x264 যদি বেশি CPU ব্যবহার করে efficient representation খুঁজে পায়, আমি সেই খরচ একবার দিতে রাজি। Browser encoder-এর search আবার করে না; সে কেবল final bitstream decode করে।
নিয়মটি সহজ হয়ে গেল: যে ধাপ একবার হয় সেখানে computation খরচ করো, আর যে ধাপ বারবার হয় সেখানে byte নিয়ে কৃপণ হও।
নতুন codec-এর পেছনে না ছুটে কেন H.264-তেই থাকলাম
আমি বলছি না H.264 সবচেয়ে compression-efficient codec। তা নয়। নতুন codec খুব আকর্ষণীয় হতে পারে যখন delivery system একাধিক rendition রাখতে পারে এবং প্রতিটি client-এর জন্য উপযুক্তটি বেছে নিতে পারে।
আমার constraint ছিল অন্য: এক URL, এক file, এক codec, আর mobile-heavy audience-এর জন্য যতটা সম্ভব কম playback সমস্যা।
এই কাজের জন্য MP4-এর ভিতরে H.264 এখনও খুব নিরাপদ baseline। Apple বর্তমানে web developers-কে Safari-তে static video-এর জন্য H.264-encoded MP4 files ব্যবহার করতে বলে। Android-এর বর্তমান documentation-ও MP4-এ H.264 support দেখায় এবং Android 6.0 ও পরের version-এ Main Profile decoder বাধ্যতামূলক করেছে; playback recommendation-এ 1280×720 at 30 fps-কে HD H.264 configuration হিসেবেও দেখানো হয়েছে। দেখুন Android supported media formats।
এর অর্থ এই নয় যে modern device Main Profile বা Level 3.1-এ সীমাবদ্ধ। উদাহরণ হিসেবে Apple-এর HLS guidance সাধারণত Main বা Baseline-এর চেয়ে High Profile পছন্দ করে। আমি Main@3.1 বেছে নিয়েছি কারণ single static MP4-এর জন্য ইচ্ছাকৃতভাবে modest decoder envelope চাই, Apple এটি বাধ্যতামূলক করেছে বলে নয়।
যে pixel থাকারই দরকার নেই, সেগুলো encode করা বন্ধ করেছি
Resolution ছিল সবচেয়ে বড় lever-গুলোর একটি। আমার ceiling হয়েছে landscape-এর জন্য প্রায় 1280×720, portrait-এর জন্য 720×1280, আর square বা mixed-orientation material-এর জন্য প্রায় 960×960।
আরও গুরুত্বপূর্ণ নিয়ম: ceiling-এ পৌঁছানোর জন্য কখনও upscale করো না।
Source যদি 900×600 হয়, তাকে 1280×720 করলে detail ফিরে আসে না। শুধু encoder-এর বর্ণনা করার জন্য আরও sample তৈরি হয়। 1920×1080 source-কে 720p class-এ নামানো যেতে পারে, কিন্তু 900×600 source প্রায় 900×600-ই থাকতে পারে। Ceiling maximum, target নয়।
এটি সহজ শোনায়, কিন্তু unnecessary pixel বাদ দেওয়া অনেক obscure encoder tweak-এর চেয়ে বেশি গুরুত্বপূর্ণ হতে পারে।
Source-এ আসলে নেই এমন frame-এর জন্য bandwidth দেওয়া বন্ধ করেছি
Frame rate-ও multiplier। কোনো animation-এ যদি প্রতি সেকেন্ডে প্রায় 16টি useful visual state থাকে, সেটিকে 30 বা 60 fps-এ store করলেই motion ভালো হয়ে যায় না। বেশিরভাগ ক্ষেত্রে শুধু repeated বা synthesized temporal sample বাড়ে, যেগুলো তবুও represent করতে হয়।
আমার policy source-এর useful cadence বজায় রাখা এবং সাধারণত 30 fps বা তার নিচে থাকা। এই ধরনের material-এ 12, 15, 16, 18, 20, 24, 25 বা 30 fps—সবই যুক্তিসঙ্গত হতে পারে, যদি সেটি সত্যিই source-কে বর্ণনা করে।
Generated output-এ আমি পরিষ্কার CFR-ও পছন্দ করি। VFR নিজে থেকে broken নয়; CFR কেবল আমার pipeline-এ timestamps, frame count, duration check, seeking এবং পরের validation সহজ করে।
একটি নির্দিষ্ট FPS-এর চেয়ে general principle বেশি গুরুত্বপূর্ণ: source-এ নেই এমন temporal information-এর জন্য bandwidth দিও না।
CRF 28 workload-এর সিদ্ধান্ত, magic number নয়
প্রতিটি clip-কে একই target bitrate-এ ঠেলে দিতে চাইনি। প্রায় static illustration এবং complex movement-এর scene একই সংখ্যক bit চায় না।
তাই x264-এর CRF mode ব্যবহার করি এবং এই bandwidth-first illustrated workload-এর জন্য প্রায় -crf 28-এ স্থির হয়েছি। FFmpeg libx264-এ CRF-কে constant-quality rate control হিসেবে document করে; দেখুন FFmpeg codec documentation।
CRF 28 ইচ্ছাকৃতভাবে aggressive। Film grain, noisy camera footage, খুব ছোট screen text বা fidelity যেখানে bandwidth-এর চেয়ে বেশি গুরুত্বপূর্ণ—সেখানে এটি blind copy করব না।
CRF 28 যে universally transparent, এমন কোনো perceptual score আমার নেই। নিজের workload থেকে যা বলতে পারি তা সীমিত: files অনেক ছোট হয়েছে এবং normal playback-এ আমার চোখে এখনও স্বাভাবিক দেখিয়েছে। এটি practical observation, CRF 28 visually lossless—এমন দাবি নয়।
veryslow encoder-এর জন্য expensive, decoder-এর জন্য স্বয়ংক্রিয়ভাবে নয়
আমার preset -preset veryslow। Slow preset x264-কে efficient prediction এবং coding decision খোঁজার বেশি সুযোগ দেয়। এর দাম encoding CPU ও সময়।
গুরুত্বপূর্ণ পার্থক্য হলো: encoder effort এবং decoder complexity একই জিনিস নয়।
x264-কে অনেক কাজ করতে দিতে পারি, আবার final stream-কে আলাদাভাবে constrain করতে পারি। আমার conservative output contract:
H.264 Main Profile
Level 3.1
8-bit yuv420p
avc1
refs = 4
B-frames = 5
B-pyramid = normal
open GOP = disabled
FFmpeg CRF, preset, tune, profile restriction, reference frame এবং B-frame আলাদাভাবে expose করে। আমি এগুলো ঠিক এভাবেই দেখি: encoder-কে গভীর search করতে দাও, playback side-কে সাধারণ রাখো।
GOP ও VBV guardrail, main quality control নয়
এই ছোট progressive clip-গুলোর জন্য maximum GOP প্রায় পাঁচ সেকেন্ড: 30 fps-এ প্রায় -g 150, 24 fps-এ -g 120, আর 16 fps-এ -g 80।
এটি workload choice, universal rule নয়। Adaptive streaming-এর constraint আলাদা; Apple HLS authoring guidance, যেমন, প্রতি দুই সেকেন্ডে IDR recommend করে। আমি সেই HLS rule short static progressive MP4-এ blind copy করি না।
আমি আরও প্রায় এই মানগুলো ব্যবহার করি:
-maxrate:v 4M
-bufsize:v 8M
এগুলো unusual bitrate spike-এর বিরুদ্ধে ceiling। এর মানে “সবকিছু 4 Mbps-এ encode করো” নয়। Normal rate allocation CRF-এর দায়িত্বে থাকে, তাই easy clip খুব ছোট হতে পারে।
MP4 container-কেও ইচ্ছাকৃতভাবে boring রেখেছি
আমি explicit avc1 ব্যবহার করি। Apple-এর current HLS documentation avc3-এর বদলে avc1-এর মতো sample format prefer করে। এটি files ছোট করার কারণ নয়, তবে conventional H.264-in-MP4 output-এর লক্ষ্যকে সমর্থন করে।
আমি -movflags +faststart-ও ব্যবহার করি। FFmpeg-এর format documentation অনুযায়ী faststart MP4-এর moov index file-এর শুরুতে নিয়ে যায়। Android HTTP streaming requirement-ও MPEG-4-এর জন্য ftyp-এর পরে moov এবং তার পরে mdat চায়।
ftyp
moov
mdat
faststart compression improve করে না। এটি progressive HTTP playback সহজ করে।
সাধারণ SDR output-এর জন্য 8-bit yuv420p ব্যবহার করি এবং BT.709 limited/video range signal করি। Clip-এ audio না থাকলে audio track তৈরি করি না। Illustrated content-এর জন্য -tune animation-ও ব্যবহার করি; এটিকে content-specific choice হিসেবে দেখি, universal compatibility contract হিসেবে নয়।
মূল FFmpeg profile
30 fps illustrated source-এর command-এর core অংশ প্রায় এমন:
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 stage ইচ্ছাকৃতভাবে hard-code করিনি। 900×600 source-কে ceiling 1280×720 বলে বড় করা উচিত নয়, আর naturally low-frame-rate animation-কে example-এ -g 150 আছে বলে 30 fps-এ force করা উচিত নয়।
Command policy-র implementation; policy নিজে নয়।
Files কেন কয়েক গুণ ছোট হলো
একটি magic flag ছিল না।
Reduction এসেছে একাধিক সিদ্ধান্ত একসাথে নিয়ে, যেগুলো আলাদা আলাদা waste সরিয়েছে: unnecessary pixel, unnecessary frame, fixed-bitrate thinking, cheap encoder settings, অতিরিক্ত frequent keyframe এবং দরকার না থাকা stream।
এই কারণেই “file-টি H.264” বলা size সম্পর্কে আশ্চর্যজনকভাবে কম তথ্য দেয়। একই source-এর দুই H.264 encode-এর size অনেক আলাদা হতে পারে, কারণ codec name resolution, frame rate, rate control, preset, GOP structure, profile বা source preparation বলে না।
আমার ক্ষেত্রে codec বদলানোর চেয়ে তার চারপাশের সিদ্ধান্ত বদলানো বেশি গুরুত্বপূর্ণ ছিল।
এই ফলাফল কী প্রমাণ করে না
আমি controlled experiment-এ প্রতিটি setting আলাদা করে isolate করিনি, তাই savings-এর ঠিক কত শতাংশ veryslow, CRF 28, resolution reduction বা frame-rate reduction থেকে এসেছে—সৎভাবে বলতে পারি না।
প্রতিটি CRF 28 output perceptually transparent—এ দাবিও করতে পারি না। “স্পষ্ট quality drop দেখিনি” হলো normal viewing size-এ এই illustrated workload-এর জন্য আমার observation, arbitrary video-এর scientific guarantee নয়।
একটি H.264 file সব site-এর সঠিক architecture—এটাও বলছি না। Multiple rendition, adaptive streaming, HDR, 4K এবং codec negotiation trade-off বদলে দেয়।
ফলাফলটি আরও narrow এবং তাই বেশি useful: short illustrated ও animated clip-এর bandwidth-first, mobile-heavy library-তে, যেখানে encoding time সস্তা এবং predictable playback গুরুত্বপূর্ণ, এই profile আমার files কয়েক গুণ ছোট করেছে এবং normal playback-এ এখনও স্বাভাবিক দেখিয়েছে।
এখন যে নিয়মটি ব্যবহার করি
আগে video optimization-কে মূলত encoder-settings problem মনে করতাম। এখন lifetime-cost problem হিসেবে দেখি।
Encoder হয়তো একবার চলবে। Byte network দিয়ে হাজার বা লাখ বার যেতে পারে।
এতে “expensive” শব্দটির মানে বদলে যায়।
CPU একবার খরচ করতে আমার সমস্যা নেই। কিন্তু প্রতিটি future request-এ upscale থেকে তৈরি pixel, useful motion না যোগ করা frame বা content-এর দরকারের চেয়ে বেশি bitrate পাঠাতে আমি অনেক কম রাজি।
Codec boring-ই রইল: MP4-এ H.264। Optimization ঘটেছে তার চারপাশে।
এই workload-এর জন্য lesson কোনো individual FFmpeg flag-এর চেয়ে পরিষ্কার: যে খরচ বারবার দাও সেটি optimize করো, যে খরচ একবার দাও সেটি নয়।