লাইভ ট্রান্সকোডিং ও প্যাকেজিং
এক সেকেন্ডের ভিডিও এক সেকেন্ডের মধ্যে — একটা ffmpeg কমান্ডে তিনটা রেন্ডিশন, keyframe মেলানো, স্লাইডিং উইন্ডো প্লেলিস্ট, লাইভ এজ, PROGRAM-DATE-TIME, আর কতগুলো স্ট্রিম একটা সার্ভারে ধরবে সেটা নিজে মাপা।
গল্পে বুঝি
বঙ্গবন্ধু আন্তর্জাতিক সম্মেলন কেন্দ্রে একটা চিকিৎসা সম্মেলন। মঞ্চে সিনা আরবিতে বক্তৃতা দিচ্ছেন, আর হলের পেছনে কাচঘেরা ছোট্ট বুথে বসে আছেন তিনজন দোভাষী: খোয়ারিজমি বাংলায়, ফাতিমা ইংরেজিতে, আর মরিয়ম সহজ ভাষায় ছাত্রদের জন্য। তিনজনই একই কণ্ঠ একবার শুনছেন, তারপর নিজের নিজের চ্যানেলে বলছেন। দর্শকেরা কানে হেডফোন লাগিয়ে চেয়ারের পাশের নব ঘুরিয়ে যার যেটা দরকার সেই চ্যানেল বেছে নেন।
এই কাজের নিয়ম একটাই, আর সেটা নির্মম: সিনা যত দ্রুত বলবেন, দোভাষীকেও তত দ্রুত বলতে হবে। খোয়ারিজমি যদি প্রতি মিনিটে মাত্র দুই সেকেন্ড করে পিছিয়ে পড়েন, প্রথম দশ মিনিটে কেউ টের পাবে না। কিন্তু এক ঘণ্টা পরে তিনি দুই মিনিট পেছনে, সিনা যখন প্রশ্নোত্তরে চলে গেছেন খোয়ারিজমি তখনো আগের স্লাইড ব্যাখ্যা করছেন। পিছিয়ে পড়া একবার শুরু হলে আর ধরা যায় না, কারণ সিনা থামবেন না। তাই অভিজ্ঞ দোভাষীরা নিজের গতির চেয়ে একটু ধীর বক্তার কাজ নেন, হাতে একটু জায়গা রাখেন।
দোভাষীরা প্রতি দুই-তিন বাক্য পর পর একটু থামেন, ঠিক যেখানে সিনা থামেন। তিনজন একই জায়গায় থামেন বলে কোনো দর্শক মাঝপথে চ্যানেল বদলালে নতুন চ্যানেলে একটা বাক্যের শুরু থেকেই শোনেন, অর্ধেক বাক্য নয়। আর বুথের দেয়ালে একটা বড় ঘড়ি, প্রতিটা অংশের পাশে খাতায় সময় লেখা হয়, যাতে পরে “দুপুর বারোটা দশে কী বলেছিলেন” খুঁজে বের করা যায়। সম্মেলনের সাইনবোর্ডে শুধু শেষ পাঁচটা ঘোষণা থাকে; পুরনোটা মুছে নতুনটা লেখা হয়।
মিলিয়ে নিই: সিনার বক্তৃতা হলো ইনকামিং লাইভ স্ট্রিম, তিনজন দোভাষী হলো ABR ladder-এর তিনটা রেন্ডিশন, আর একই কণ্ঠ একবার শুনে তিনজনের ভাগ করে নেওয়া হলো একবার ডিকোড করে split করা। বক্তার গতির সাথে তাল রাখার বাধ্যবাধকতা হলো রিয়েল-টাইম শর্ত (speed ১.০x-এর উপরে), আর ধীরে ধীরে পিছিয়ে পড়া হলো encode lag জমা। তিনজনের একই জায়গায় থামা হলো সব রেন্ডিশনে keyframe মেলানো, যাতে চ্যানেল বদল মানে ABR সুইচ নির্বিঘ্ন হয়। দেয়ালের ঘড়ি আর খাতার সময় হলো EXT-X-PROGRAM-DATE-TIME, আর শেষ পাঁচটা ঘোষণার বোর্ড হলো স্লাইডিং উইন্ডো প্লেলিস্ট। বাস্তবে প্রতিটা লাইভ প্ল্যাটফর্মের ট্রান্সকোডার ঠিক এই কাজটাই করে, প্রতি সেকেন্ডে, পুরো সম্প্রচার জুড়ে।
লাইভ ট্রান্সকোডার আসলে কী করে
ইনজেস্ট সার্ভার (চ্যাপ্টার ৪) এনকোডার থেকে একটা মাত্র স্ট্রিম পায়, সাধারণত ১০৮০p, একটা বিটরেটে। দর্শকদের দরকার একাধিক কোয়ালিটি, যাতে খারাপ নেটওয়ার্কের মোবাইলও কিছু একটা দেখতে পায়। মাঝের এই কাজটা লাইভ ট্রান্সকোডারের:
VOD-র ট্রান্সকোডিং (ভিডিও ট্র্যাকের চ্যাপ্টার ৪) থেকে এর পার্থক্য তিনটা:
- ফাইলের শেষ নেই। ইনপুট কখন শেষ হবে কেউ জানে না, তাই দুই-পাস এনকোডিং, পুরো ফাইল দেখে per-title ladder, এসব চলে না।
- পরে ঠিক করার সুযোগ নেই। VOD-তে একটা রেন্ডিশন খারাপ হলে আবার চালানো যায়। লাইভে যে সেকেন্ড চলে গেছে সেটা চলেই গেছে।
- আউটপুট প্রতি কয়েক সেকেন্ডে বদলায়। প্লেলিস্ট একবার লেখা হয় না, প্রতিটা নতুন সেগমেন্টের সাথে আবার লেখা হয়।
কেন preset দ্রুত রাখতে হয়, কেন CBR, কেন lookahead আর B-frame সীমিত, সেই যুক্তিগুলো ভিডিও ট্র্যাকের চ্যাপ্টার ৯-এ আছে, এখানে আর পুনরাবৃত্তি করছি না। এখানে দেখব কাজটা হাতে-কলমে কীভাবে করবেন।
রিয়েল-টাইম শর্ত: speed= পড়তে শিখুন
ffmpeg চলার সময় নিচে একটা লাইন বারবার আপডেট হয়। লাইভ ট্রান্সকোডারের স্বাস্থ্যের সবচেয়ে গুরুত্বপূর্ণ সংখ্যাটা ওখানেই:
frame= 720 fps= 31 q=29.0 Lq=23.0 q=22.0 size=N/A time=00:00:24.00 bitrate=N/A speed=1.02x elapsed=0:00:23.54 speed=1.02x মানে মিডিয়ার ১ সেকেন্ড প্রসেস হতে বাস্তবের ০.৯৮ সেকেন্ড লেগেছে। লাইভে এটা ১.০x-এর কাছাকাছি থাকবেই, কারণ ইনপুট নিজেই রিয়েল-টাইমে আসছে; ট্রান্সকোডার এর চেয়ে দ্রুত যেতে পারে না, কারণ ভবিষ্যতের ফ্রেম এখনো আসেনি। তাই চলমান লাইভ স্ট্রিমে speed=1.0x দেখে নিশ্চিন্ত হওয়ার কিছু নেই। এটা শুধু বলে যে এই মুহূর্তে পিছিয়ে নেই, কতটা জায়গা হাতে আছে তা বলে না।
যেটা জানা দরকার সেটা হলো হেডরুম: ইনপুট যদি সীমাহীন দ্রুত আসত, এই মেশিন এই ladder কত গুণ দ্রুত চালাতে পারত? সেটা মাপতে -re (ইনপুট রিয়েল-টাইমে পড়ো) বাদ দিয়ে একই এনকোডিং চালান, আউটপুট ফেলে দিন:
ffmpeg -hide_banner -i sample-real-footage.mp4 -t 60 \
-filter_complex "[0:v]split=3[v1][v2][v3];[v2]scale=w=1280:h=720[v2o];[v3]scale=w=854:h=480[v3o]" \
-map "[v1]" -c:v:0 libx264 -b:v:0 5000k \
-map "[v2o]" -c:v:1 libx264 -b:v:1 2800k \
-map "[v3o]" -c:v:2 libx264 -b:v:2 1200k \
-preset veryfast -tune zerolatency -g 60 -sc_threshold 0 \
-f null - একটা Apple M4 ল্যাপটপে ffmpeg-এর কৃত্রিম testsrc2 ছবি দিয়ে এটা চালিয়ে পাওয়া আসল আউটপুট:
frame= 600 fps=306 q=29.0 Lq=23.0 q=22.0 size=N/A time=00:00:20.00 bitrate=N/A speed=10.2x elapsed=0:00:01.95 ১০.২x মানে কাগজে-কলমে এই মেশিন এমন দশটা স্ট্রিম একসাথে চালাতে পারত। কিন্তু এই সংখ্যাটা বিশ্বাস করবেন না। কৃত্রিম টেস্ট প্যাটার্ন এনকোড করা খুব সহজ: বড় সমতল রঙ, ধীরে চলা নকশা। আসল ক্রিকেট ম্যাচে ঘাস, ভিড়, দ্রুত প্যান থাকে, আর এনকোডারের কাজ কয়েক গুণ বেড়ে যায়। হেডরুম মাপতে সবসময় আপনার আসল কন্টেন্টের সবচেয়ে কঠিন অংশ ব্যবহার করুন: খেলার সবচেয়ে দ্রুত দৃশ্য, কনসার্টের আলোর ঝলকানি, বৃষ্টি।
চলমান স্ট্রিমে speed= ১.০x-এর নিচে নামা মানে ট্রান্সকোডার পিছিয়ে পড়ছে, আর ইনপুট বাফার জমছে। কিছুক্ষণ পরে হয় ইনজেস্ট সার্ভার কানেকশন কেটে দেবে, নয়তো দর্শকের latency মিনিটে মিনিটে বাড়বে। speed= লগ থেকে পার্স করে মেট্রিক হিসেবে পাঠান (চ্যাপ্টার ১২), আর ০.৯৮x-এর নিচে কয়েক সেকেন্ড থাকলেই অ্যালার্ট দিন।
লাইভ ladder: কম রাং, বেশি চিন্তা
VOD-তে একটা ভিডিওর জন্য ৬-৮টা রেন্ডিশন বানানো স্বাভাবিক, কারণ এনকোড হয় একবার, দেখা হয় হাজারবার। লাইভে প্রতিটা রাং প্রতিটা সেকেন্ডে, পুরো সম্প্রচার জুড়ে CPU খায়। তিন ঘণ্টার ম্যাচে একটা বাড়তি রাং মানে তিন ঘণ্টা বাড়তি একটা পূর্ণ এনকোড। তাই লাইভ ladder সাধারণত ছোট:
| রাং | রেজোলিউশন | ভিডিও বিটরেট (উদাহরণ) | কার জন্য |
|---|---|---|---|
| উপরের | ১০৮০p৩০ | ৪.৫-৬ Mbps | ব্রডব্যান্ড, বড় স্ক্রিন |
| মাঝের | ৭২০p৩০ | ২.৫-৩.৫ Mbps | ভালো ৪G, ল্যাপটপ |
| নিচের | ৪৮০p৩০ | ১-১.৫ Mbps | দুর্বল মোবাইল নেটওয়ার্ক |
| ঐচ্ছিক | ৩৬০p | ৫০০-৮০০ kbps | খুব দুর্বল নেটওয়ার্ক, শুধু ভিড়ের দিন |
সংখ্যাগুলো উদাহরণ, নিয়ম নয়; ladder কীভাবে বেছে নেবেন তার পুরো যুক্তি ভিডিও ট্র্যাকের চ্যাপ্টার ৫-এ। লাইভে বাড়তি দুটো কথা মাথায় রাখুন:
- উপরের রাং সোর্সের চেয়ে বেশি হতে পারে না। এনকোডার ৪ Mbps-এ ১০৮০p পাঠালে ট্রান্সকোডারে ৬ Mbps-এর ১০৮০p বানানো মানে শুধু বেশি বাইট, বেশি মান নয়।
- রাংগুলোর মাঝে অন্তত দেড় থেকে দুই গুণ ফারাক রাখুন। খুব কাছাকাছি রাং মানে প্লেয়ার বারবার এদিক-ওদিক সুইচ করবে, কিন্তু দর্শক কোনো উন্নতি দেখবে না।
সোর্স পাসথ্রু: উপরের রাং এনকোড না করা
একটা প্রচলিত কৌশল হলো উপরের রাংটা আবার এনকোড না করে এনকোডার যা পাঠিয়েছে সেটাই সরাসরি কপি করা (-c:v copy)। এতে সবচেয়ে দামি এনকোডটা বেঁচে যায়, আর উপরের রাংয়ের মান সোর্সের সমান থাকে।
দাম হলো নিয়ন্ত্রণ হারানো। কপি করা রাংয়ের keyframe কোথায় পড়বে সেটা ঠিক করে এনকোডার, আপনি নন। অন্য রাংগুলোর keyframe যদি ঠিক একই ফ্রেমে না পড়ে, তাহলে ABR সুইচে ছবি ভাঙবে বা আটকাবে। আর এনকোডার যদি keyint ঠিকমতো না সেট করে (অনেক OBS প্রোফাইলে “auto”), সেগমেন্টের দৈর্ঘ্যও এলোমেলো হবে। পাসথ্রু তখনই নিন যখন এনকোডারের সেটিং আপনার নিয়ন্ত্রণে, আর ইনজেস্টে keyframe ইন্টারভাল যাচাই করা হয় (চ্যাপ্টার ৩ ও ৪)। নিচের রাংগুলোয় তখন -force_key_frames "source" দিয়ে সোর্সের keyframe-এর জায়গাতেই keyframe বসাতে হয়।
একটা কমান্ডে তিনটা রেন্ডিশন
নিচের কমান্ডটা একটা RTMP ইনপুট থেকে তিন রেন্ডিশনের লাইভ HLS বানায়। স্থানীয়ভাবে পরীক্ষা করার জন্য আমি ইনপুটের জায়গায় ffmpeg-এর টেস্ট সোর্স বসিয়ে এটা আসলেই চালিয়েছি; আউটপুট নিচে দেওয়া আছে।
ffmpeg -hide_banner \
-i rtmp://127.0.0.1:1935/live/stream-key-here \
-filter_complex "[0:v]split=3[v1][v2][v3];[v2]scale=w=1280:h=720[v2o];[v3]scale=w=854:h=480[v3o]" \
-map "[v1]" -c:v:0 libx264 -b:v:0 5000k -maxrate:v:0 5000k -bufsize:v:0 10000k \
-map "[v2o]" -c:v:1 libx264 -b:v:1 2800k -maxrate:v:1 2800k -bufsize:v:1 5600k \
-map "[v3o]" -c:v:2 libx264 -b:v:2 1200k -maxrate:v:2 1200k -bufsize:v:2 2400k \
-preset veryfast -tune zerolatency \
-g 60 -keyint_min 60 -sc_threshold 0 \
-map 0:a -map 0:a -map 0:a -c:a aac -b:a 128k -ar 48000 \
-f hls \
-hls_time 2 \
-hls_list_size 6 \
-hls_flags delete_segments+independent_segments+program_date_time+temp_file \
-hls_segment_filename "/srv/live/stream_%v/seg_%05d.ts" \
-master_pl_name master.m3u8 \
-var_stream_map "v:0,a:0,name:1080p v:1,a:1,name:720p v:2,a:2,name:480p" \
"/srv/live/stream_%v/index.m3u8" প্রতিটা অংশ কী করছে:
split=3— ডিকোড একবার হয়, তারপর একই ফ্রেম তিন ভাগে যায়। তিনবার আলাদা ডিকোড করলে CPU নষ্ট।-c:v:0,-b:v:0ইত্যাদি —:0,:1,:2হলো আউটপুটের ভিডিও স্ট্রিমের সূচক; প্রতিটা রাংয়ের আলাদা বিটরেট এভাবে বসে।-maxrateও-bufsize— বিটরেটকে একটা সীমার মধ্যে রাখে, যাতে হঠাৎ জটিল দৃশ্যে বিটরেট লাফিয়ে দর্শকের ছোট বাফার শুকিয়ে না দেয়।-g 60 -keyint_min 60 -sc_threshold 0— ৩০ fps-এ প্রতি ৬০ ফ্রেমে, মানে ঠিক ২ সেকেন্ডে, একটা keyframe, আর দৃশ্য বদলালেও বাড়তি keyframe নয়। এই তিনটা একসাথে না থাকলে রাংগুলোর keyframe আলাদা জায়গায় পড়তে পারে।-map 0:aতিনবার —var_stream_map-এ প্রতিটা ভেরিয়েন্টের জন্য একটা করে অডিও স্ট্রিম লাগে, তাই একই অডিও তিনবার ম্যাপ করা হয়েছে। আলাদা অডিও রেন্ডিশন গ্রুপ (agroup) দিয়েও করা যায়, তখন অডিও একবারই এনকোড হয়।-hls_time 2— টার্গেট সেগমেন্ট দৈর্ঘ্য। আসল কাটাটা হয় keyframe-এ, তাই-gআর-hls_timeমিলতে হবে।-hls_list_size 6— প্লেলিস্টে শেষ ছয়টা সেগমেন্ট, মানে ১২ সেকেন্ডের জানালা।-hls_flags—delete_segmentsপুরনো ফাইল মুছে দেয়;independent_segmentsপ্লেলিস্টে ঘোষণা করে যে প্রতিটা সেগমেন্ট নিজে থেকে ডিকোড করা যায়;program_date_timeপ্রতিটা সেগমেন্টে দেয়ালের ঘড়ির সময় বসায়;temp_fileআগে অস্থায়ী নামে লিখে তারপর নাম বদলায়, যাতে অর্ধেক লেখা ফাইল কেউ না পায়।%v—var_stream_map-এরname:এখানে বসে, তাই ফোল্ডার হয়stream_1080p,stream_720p,stream_480p।
ffmpeg-এর HLS মাক্সারের এই অপশনগুলো ২০২৬ সালের অক্টোবরে ffmpeg 9.0.2-এ ffmpeg -h muxer=hls দিয়ে মিলিয়ে দেখা। ভার্সনভেদে অপশন বদলায়, তাই নিজের ভার্সনে একই কমান্ড চালিয়ে মিলিয়ে নিন।
আসল চালানোর পর মাস্টার প্লেলিস্ট:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=5539232,AVERAGE-BANDWIDTH=5350291,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
stream_1080p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3193744,AVERAGE-BANDWIDTH=3076995,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
stream_720p/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1490464,AVERAGE-BANDWIDTH=1424789,RESOLUTION=854x480,CODECS="avc1.64001f,mp4a.40.2"
stream_480p/index.m3u8 খেয়াল করুন CODECS অ্যাট্রিবিউট ffmpeg নিজেই বসিয়েছে। প্লেয়ার এটা দেখে আগেই বুঝে নেয় ডিভাইস এই কোডেক চালাতে পারবে কিনা। আর BANDWIDTH এসেছে আসল এনকোড থেকে, তাই এটা -b:v-এর চেয়ে একটু বেশি (অডিও আর কন্টেইনারের ওভারহেড যোগ হয়েছে)।
সব রেন্ডিশনে keyframe মিলছে কিনা যাচাই
ABR সুইচ নির্বিঘ্ন হওয়ার শর্ত হলো একই নম্বরের সেগমেন্ট সব রাংয়ে একই মুহূর্তে শুরু হওয়া। ffprobe দিয়ে প্রতিটা রাংয়ের একই সেগমেন্টের প্রথম প্যাকেট দেখুন:
for r in 1080p 720p 480p; do
echo -n "$r first pts: "
ffprobe -v error -select_streams v \
-show_entries packet=pts_time,flags -of csv=p=0 \
stream_$r/seg_00008.ts | head -1
done আসল আউটপুট:
1080p first pts: 17.421333,K__,
720p first pts: 17.421333,K__,
480p first pts: 17.421333,K__, তিনটাই একই সময়ে শুরু, আর K মানে প্রথম প্যাকেটটা keyframe। কোনো রাংয়ে সময় আলাদা হলে বা K না থাকলে সুইচের সময় সমস্যা হবেই।
সেগমেন্ট কত লম্বা হবে
সেগমেন্টের দৈর্ঘ্য একসাথে তিনটা জিনিস ঠিক করে: latency, CDN-এ রিকোয়েস্টের সংখ্যা, আর keyframe-এর খরচ।
| সেগমেন্ট | সাধারণ latency (৩টা সেগমেন্ট পিছিয়ে) | প্রতি দর্শকের রিকোয়েস্ট/মিনিট (একটা রাং) | keyframe-এর খরচ |
|---|---|---|---|
| ৬ সেকেন্ড | ২০ সেকেন্ডের বেশি | ১০ সেগমেন্ট + ~১০ প্লেলিস্ট | সবচেয়ে কম |
| ৪ সেকেন্ড | ১৪-১৮ সেকেন্ড | ১৫ + ~১৫ | মাঝারি |
| ২ সেকেন্ড | ৮-১০ সেকেন্ড | ৩০ + ~৩০ | বেশি |
হিসাবটা সরল: ২ সেকেন্ডের সেগমেন্টে প্রতি মিনিটে ৩০টা সেগমেন্ট, আর প্লেয়ার মোটামুটি প্রতি সেগমেন্ট দৈর্ঘ্যে একবার প্লেলিস্ট রিফ্রেশ করে, তাই আরও প্রায় ৩০টা। এক লাখ দর্শক হলে CDN-এ প্রতি মিনিটে প্রায় ৬০ লাখ রিকোয়েস্ট। latency সংখ্যাগুলো মোটামুটি; আসল সংখ্যা প্লেয়ার, নেটওয়ার্ক আর CDN-এর উপর নির্ভর করে। latency কোথায় কোথায় খরচ হয় তার ধাপে ধাপে হিসাব ভিডিও ট্র্যাকের চ্যাপ্টার ৯-এ।
বেশিরভাগ লাইভ ইভেন্টের জন্য ২ থেকে ৪ সেকেন্ড একটা ভালো শুরু। এর চেয়ে কম latency চাইলে সেগমেন্ট আরও ছোট না করে low-latency প্রোটোকল ব্যবহার করুন (পরের চ্যাপ্টার)।
স্লাইডিং উইন্ডো প্লেলিস্ট
লাইভ প্লেলিস্টের শেষ নেই, তাই সব সেগমেন্ট এতে থাকতে পারে না। এতে থাকে শুধু সাম্প্রতিক কয়েকটা, আর প্রতিটা নতুন সেগমেন্ট এলে সবচেয়ে পুরনোটা বাদ পড়ে। আসল চালানো থেকে ৭২০p-এর প্লেলিস্ট:
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-TARGETDURATION:2
#EXT-X-MEDIA-SEQUENCE:6
#EXT-X-INDEPENDENT-SEGMENTS
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:47:59.501+0600
seg_00006.ts
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:48:01.501+0600
seg_00007.ts
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:48:03.501+0600
seg_00008.ts
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:48:05.501+0600
seg_00009.ts
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:48:07.501+0600
seg_00010.ts
#EXTINF:2.000000,
#EXT-X-PROGRAM-DATE-TIME:2026-10-25T10:48:09.501+0600
seg_00011.ts
#EXT-X-ENDLIST তিনটা জিনিস লক্ষ করুন।
EXT-X-MEDIA-SEQUENCE:6 বলে প্রথম তালিকাভুক্ত সেগমেন্টের ক্রমিক নম্বর ৬। পরের আপডেটে এটা ৭ হবে, আর seg_00006.ts তালিকা থেকে বাদ যাবে। প্লেয়ার এই নম্বর দিয়েই বোঝে আগেরবার কোথায় ছিল আর কোনগুলো নতুন। লাইভ চলার মাঝে এই নম্বর কখনো কমা বা লাফানো উচিত নয়; সেটা হলে প্লেয়ার বিভ্রান্ত হয়ে আটকে যায় বা একই অংশ আবার চালায়।
ডিস্কে ফাইল প্লেলিস্টের চেয়ে একটা বেশি। ফোল্ডারে seg_00005.ts তখনো ছিল, যদিও প্লেলিস্টে নেই। এটা ইচ্ছাকৃত: hls_delete_threshold (ডিফল্ট ১) বলে তালিকা থেকে বাদ পড়ার পরও কয়টা সেগমেন্ট রেখে দিতে হবে, কারণ কোনো ধীর প্লেয়ার হয়তো পুরনো প্লেলিস্ট দেখে ঠিক ওটাই চাইছে। CDN-এর পেছনে প্লেলিস্ট কয়েক সেকেন্ড ক্যাশ হলে এই সংখ্যা বাড়ান, নাহলে দর্শক 404 পাবে।
শেষে EXT-X-ENDLIST। আমার পরীক্ষায় ইনপুট ২৪ সেকেন্ড পর শেষ হয়েছিল, তাই ffmpeg বেরোনোর সময় এটা লিখেছে। লাইভে এটা একটা ফাঁদ, নিচে আলাদা করে দেখছি।
জানালা কত বড় হবে
hls_list_size খুব ছোট রাখলে (ধরুন ৩) প্লেয়ারের হাতে জায়গা কম থাকে: নেটওয়ার্ক একটু থমকালে যে সেগমেন্ট সে চাইবে সেটা ততক্ষণে মুছে গেছে। খুব বড় রাখলে প্লেলিস্ট ফাইল বড় হয় আর প্রতি রিফ্রেশে বেশি বাইট যায়। প্রচলিত শুরু হলো ৩০ সেকেন্ড থেকে এক মিনিটের জানালা (২ সেকেন্ডের সেগমেন্টে ১৫-৩০টা)। দর্শককে পেছনে টানতে দিতে চাইলে সেটা আলাদা বিষয়, DVR, চ্যাপ্টার ৭-এ।
লাইভ এজ আর HOLD-BACK
প্লেয়ার যখন লাইভ স্ট্রিমে ঢোকে, সে শেষ সেগমেন্ট থেকে চালানো শুরু করে না। শেষ সেগমেন্ট মাত্রই লেখা হয়েছে, পরেরটা এখনো তৈরি হচ্ছে, তাই একেবারে শেষ থেকে শুরু করলে প্রথম সেকেন্ডেই বাফার শেষ হয়ে আটকে যাবে। HLS-এর নিয়ম অনুযায়ী প্লেয়ার শেষ থেকে অন্তত তিনটা টার্গেট ডিউরেশন পেছনে থেকে শুরু করে। ২ সেকেন্ডের সেগমেন্টে সেটা ৬ সেকেন্ড।
এই পেছনে থাকার দূরত্বটা সার্ভার নিজেও ঘোষণা করতে পারে:
#EXT-X-SERVER-CONTROL:HOLD-BACK=6.0 HOLD-BACK বলে প্লেয়ার লাইভ এজ থেকে কমপক্ষে কত সেকেন্ড পেছনে থাকবে। এটাই কার্যত আপনার স্ট্রিমের ন্যূনতম latency-র একটা বড় অংশ। প্লেয়ার কীভাবে এই দূরত্ব বজায় রাখে, পিছিয়ে পড়লে কীভাবে ধরে ফেলে, সেটা পরের চ্যাপ্টারে।
EXT-X-PROGRAM-DATE-TIME: প্রতিটা সেগমেন্টের দেয়াল-ঘড়ি
EXTINF বলে সেগমেন্ট কত লম্বা, কিন্তু কখন তৈরি হয়েছিল তা বলে না। PROGRAM-DATE-TIME (সংক্ষেপে PDT) প্রতিটা সেগমেন্টের প্রথম ফ্রেমকে বাস্তবের একটা তারিখ-সময়ের সাথে বেঁধে দেয়। লাইভে এটা প্রায় সবকিছুর ভিত্তি:
- latency মাপা। প্লেয়ার এখন যে ফ্রেম দেখাচ্ছে তার PDT আর এখনকার সময়ের পার্থক্যই glass-to-glass latency-র কাছাকাছি একটা মাপ।
- ক্লিপ কাটা। “সন্ধ্যা ৭টা ১২ থেকে ৭টা ১৪-এর হাইলাইট” কোন সেগমেন্টে আছে, সেটা PDT দেখে বের করা যায় (চ্যাপ্টার ৭)।
- সিঙ্ক। চ্যাট, স্কোর আর অন্য ক্যামেরা অ্যাঙ্গেল একই ঘড়িতে মেলানো (চ্যাপ্টার ১০)।
- রিডানডেন্ট এনকোডারের সাথে মেলানো। দুইটা ট্রান্সকোডারের আউটপুট একই মুহূর্তে মেলাতে (চ্যাপ্টার ৯)।
আসল আউটপুটে সময়টা ছিল 2026-10-25T10:47:59.501+0600: ffmpeg মেশিনের স্থানীয় টাইমজোন বসিয়েছে। স্পেক অনুযায়ী এটা বৈধ, কিন্তু একাধিক অঞ্চলের সার্ভার মেলাতে হলে UTC-তে চালানো সহজ। আর সবচেয়ে জরুরি: ট্রান্সকোডার মেশিনের ঘড়ি NTP দিয়ে মিলিয়ে রাখুন। ঘড়ি দুই সেকেন্ড পিছিয়ে থাকলে আপনার latency মেট্রিক দুই সেকেন্ড ভুল, আর ক্লিপও দুই সেকেন্ড সরে কাটা হবে, কেউ টের পাবে না।
ফাঁদ: ffmpeg বন্ধ হলে ENDLIST
উপরের প্লেলিস্টের শেষে #EXT-X-ENDLIST দেখেছেন। ffmpeg-এর HLS মাক্সার বেরোনোর সময় এটা লিখে দেয়, মানে “স্ট্রিম শেষ”। VOD-তে এটাই চাই। কিন্তু লাইভে ভাবুন: এনকোডারের নেটওয়ার্ক পাঁচ সেকেন্ডের জন্য কেটে গেল, ইনপুট বন্ধ হলো, ffmpeg বেরিয়ে গেল আর ENDLIST লিখে দিল। এবার যে প্লেয়ারগুলো প্লেলিস্ট রিফ্রেশ করল তারা ভাবল সম্প্রচার শেষ, আর রিফ্রেশ করাই বন্ধ করে দিল। এনকোডার ফিরে এল, ffmpeg আবার চালু হলো, কিন্তু দর্শকের স্ক্রিনে “শেষ”। তাদের পেজ রিলোড করতে হবে।
দুটো ব্যবস্থা নিন:
omit_endlistফ্ল্যাগ যোগ করুন, যাতে ffmpeg বেরোনোর সময়ENDLISTনা লেখে। সম্প্রচার সত্যিই শেষ হলে আপনার কন্ট্রোল প্লেন আলাদাভাবেENDLISTযোগ করবে।- রিস্টার্টে
MEDIA-SEQUENCEআর ফাইলের নাম যেন আবার ০ থেকে শুরু না হয়। আবার ০ থেকে শুরু হলে প্লেয়ার দেখে সিকোয়েন্স পিছিয়ে গেছে, আর পুরনো নামের ফাইল CDN-এ ক্যাশড থাকলে দর্শক পুরনো ভিডিও দেখে।-hls_start_number_source epochদিয়ে শুরুর নম্বর ঘড়ি থেকে নিন, যাতে প্রতিবার রিস্টার্টে নম্বর সামনেই এগোয়। আর রিস্টার্টের জায়গায়discont_startফ্ল্যাগ দিয়েEXT-X-DISCONTINUITYবসান, যাতে প্লেয়ার জানে টাইমস্ট্যাম্প নতুন করে শুরু হয়েছে।
-hls_flags delete_segments+independent_segments+program_date_time+temp_file+omit_endlist+discont_start \
-hls_start_number_source epoch ইনপুট কেটে গেলে কী হয় আর কীভাবে ব্যাকআপ ইনপুটে চলে যেতে হয়, তার পুরো আলোচনা চ্যাপ্টার ৯-এ।
TS না fMP4
উপরের উদাহরণে সেগমেন্ট .ts (MPEG-TS), কারণ এটা সবচেয়ে পুরনো আর সব জায়গায় চলে। -hls_segment_type fmp4 দিলে ffmpeg fragmented MP4 সেগমেন্ট আর একটা init.mp4 বানায়। fMP4 মানে CMAF, তাই একই সেগমেন্ট HLS আর DASH দুটোতেই চলে, আর LL-HLS-এর partial segment-ও fMP4-এ সহজ। নতুন সিস্টেমে fMP4 দিয়ে শুরু করাই ভালো। CMAF কেন আর কীভাবে, সেটা ভিডিও ট্র্যাকের চ্যাপ্টার ৬-এ।
GPU আর হার্ডওয়্যার এনকোডার
CPU-তে libx264 সবচেয়ে ভালো মান দেয় প্রতি বিটে, কিন্তু প্রতিটা স্ট্রিম কয়েকটা কোর খেয়ে ফেলে। অনেক স্ট্রিম একসাথে চালাতে হলে হার্ডওয়্যার এনকোডার:
| এনকোডার | কোথায় | ffmpeg-এ নাম |
|---|---|---|
| NVENC | NVIDIA GPU | h264_nvenc, hevc_nvenc |
| Quick Sync | Intel CPU-র ভেতরের GPU | h264_qsv |
| VA-API | Linux-এ Intel/AMD GPU | h264_vaapi |
| VideoToolbox | Apple Silicon | h264_videotoolbox |
তিনটা জিনিস মাথায় রাখুন:
- একই বিটরেটে মান সাধারণত একটু কম। একই মান পেতে কিছুটা বেশি বিটরেট লাগতে পারে। নিজের কন্টেন্টে পাশাপাশি তুলনা করে দেখুন (VMAF দিয়ে মাপা, ভিডিও ট্র্যাকের চ্যাপ্টার ১০)।
- ডিকোড আর স্কেলও GPU-তে রাখুন। CPU-তে ডিকোড করে প্রতিটা ফ্রেম GPU-তে পাঠানো আর ফেরত আনা মেমরি বাসে বড় চাপ, অনেক সময় এটাই বটলনেক হয়।
- একসাথে কয়টা এনকোড সেশন চলবে, তার সীমা থাকতে পারে। কিছু গ্রাহক-পর্যায়ের GPU-তে ড্রাইভার একসাথে চলা এনকোড সেশনের সংখ্যা সীমিত রাখে, আর এই সীমা সময়ের সাথে বদলেছে। কেনার আগে নির্মাতার বর্তমান তালিকা দেখে নিন।
একটা সার্ভারে কতগুলো স্ট্রিম ধরবে: নিজে মাপুন
এই প্রশ্নের কোনো সাধারণ উত্তর নেই, কারণ এটা নির্ভর করে কন্টেন্ট, ladder, preset, CPU-র প্রজন্ম আর কোরের সংখ্যার উপর। ইন্টারনেটে পাওয়া কোনো সংখ্যা আপনার জন্য ঠিক হবে না। কিন্তু মাপার পদ্ধতিটা সহজ:
- আপনার আসল কন্টেন্টের একটা কঠিন ৬০ সেকেন্ডের ক্লিপ নিন।
- প্রোডাকশনের ladder আর সেটিং দিয়ে,
-reছাড়া, একসাথে N-টা ffmpeg চালান। - প্রত্যেকের শেষ
speed=দেখুন। সবচেয়ে ধীরটা যতক্ষণ ১.৩-১.৫x-এর উপরে, ততক্ষণ N বাড়ান। - যেখানে সবচেয়ে ধীরটা ১.৩x-এর নিচে নামে, তার আগের N-টাই এই মেশিনের নিরাপদ সংখ্যা।
#!/usr/bin/env bash
# Usage: ./capacity.sh hard-clip.mp4 4 (runs 4 ladders in parallel, prints each final speed)
set -euo pipefail
clip="$1"; n="$2"
for i in $(seq 1 "$n"); do
ffmpeg -hide_banner -nostats -progress pipe:1 -i "$clip" -t 60 \
-filter_complex "[0:v]split=3[v1][v2][v3];[v2]scale=w=1280:h=720[v2o];[v3]scale=w=854:h=480[v3o]" \
-map "[v1]" -c:v:0 libx264 -b:v:0 5000k -map "[v2o]" -c:v:1 libx264 -b:v:1 2800k \
-map "[v3o]" -c:v:2 libx264 -b:v:2 1200k -preset veryfast -tune zerolatency -g 60 -sc_threshold 0 \
-f null - 2>/dev/null | grep '^speed=' | tail -1 | sed "s/^/stream $i: /" &
done
wait ৩০-৫০% হেডরুম কেন? কারণ বাস্তবের কন্টেন্ট আপনার পরীক্ষার ক্লিপের চেয়েও কঠিন হতে পারে, মেশিনে লগ পাঠানো আর মনিটরিংও CPU খায়, আর একটা স্ট্রিম পিছিয়ে পড়লে সেটা ফেরানো যায় না।
ডিবাগিং: এই চ্যাপ্টারের সমস্যাগুলো
| লক্ষণ | কীভাবে নিশ্চিত হবেন | সমাধান |
|---|---|---|
| দর্শকের latency সময়ের সাথে বাড়ছে | ট্রান্সকোডার লগে speed= ১.০x-এর নিচে; প্লেলিস্টে নতুন সেগমেন্ট আসছে টার্গেট ডিউরেশনের চেয়ে ধীরে | ladder ছোট করুন, preset দ্রুত করুন, হার্ডওয়্যার এনকোডার নিন, বা স্ট্রিম অন্য মেশিনে সরান |
| ABR সুইচে ছবি আটকায় বা ভাঙে | প্রতিটা রাংয়ের একই সেগমেন্টের প্রথম প্যাকেট ffprobe দিয়ে দেখুন; সময় আলাদা বা K নেই | -g, -keyint_min একই রাখুন, -sc_threshold 0; পাসথ্রু রাং থাকলে বাকিগুলোয় -force_key_frames "source" |
| সেগমেন্টের দৈর্ঘ্য এলোমেলো (১.৮, ৩.৬, ২.৪…) | প্লেলিস্টের EXTINF মানগুলো দেখুন | keyframe ইন্টারভাল -hls_time-এর সাথে মেলান; ইনপুটে VFR হলে -r দিয়ে ফ্রেম রেট বেঁধে দিন |
| দর্শক সেগমেন্টে 404 পাচ্ছে | CDN লগে 404 হওয়া সেগমেন্ট নম্বর প্লেলিস্টের MEDIA-SEQUENCE-এর চেয়ে ছোট | hls_list_size বাড়ান, hls_delete_threshold বাড়ান, প্লেলিস্টের CDN TTL কমান |
| ক্ষণিক বিরতির পর সব দর্শকের স্ক্রিনে স্ট্রিম “শেষ” | প্লেলিস্টের শেষে #EXT-X-ENDLIST | omit_endlist যোগ করুন; শুধু সম্প্রচার শেষ হলে আলাদাভাবে ENDLIST লিখুন |
| ট্রান্সকোডার রিস্টার্টের পর প্লেয়ার আটকে যায় বা পুরনো অংশ দেখায় | রিস্টার্টের আগে-পরে MEDIA-SEQUENCE দেখুন; ০-তে ফিরে গেছে | -hls_start_number_source epoch আর discont_start |
| প্লেয়ার মাঝেমাঝে প্লেলিস্ট পার্স করতে পারে না | CDN বা origin থেকে আসা প্লেলিস্ট অর্ধেক কাটা | temp_file ফ্ল্যাগ, যাতে নাম বদলের আগে ফাইল সম্পূর্ণ লেখা হয় |
| latency মেট্রিক বা ক্লিপের সময় কয়েক সেকেন্ড ভুল | ট্রান্সকোডারে timedatectl বা chronyc tracking; ঘড়ি মেলানো নেই | NTP চালু করুন; সব মেশিন UTC-তে রাখুন |
| মাস্টার প্লেলিস্টে কোনো রাং নেই বা ফোল্ডারের নাম ভুল | var_stream_map-এ ভেরিয়েন্টের সংখ্যা আর -map-এর সংখ্যা মেলান | প্রতিটা ভেরিয়েন্টের জন্য একটা ভিডিও আর একটা অডিও স্ট্রিম ম্যাপ করুন |
মূল শিক্ষা
- লাইভ ট্রান্সকোডারের প্রথম নিয়ম রিয়েল-টাইম। চলমান স্ট্রিমে
speed=১.০x-এর কাছাকাছি থাকবেই; আসল প্রশ্ন হেডরুম, সেটা-reছাড়া আসল কন্টেন্ট দিয়ে মাপুন। - লাইভ ladder ছোট রাখুন। প্রতিটা রাং পুরো সম্প্রচার জুড়ে CPU খায়; উপরের রাং সোর্সের চেয়ে বেশি হবে না।
- একবার ডিকোড,
splitকরে ভাগ। তারপর প্রতিটা রাংয়ের নিজস্ব স্কেল আর এনকোড। - সব রাংয়ে keyframe একই জায়গায়।
-g,-keyint_min,-sc_threshold 0একসাথে, আরffprobeদিয়ে যাচাই। - সেগমেন্টের দৈর্ঘ্য তিনটা জিনিস ঠিক করে: latency, CDN রিকোয়েস্ট, keyframe খরচ। ২-৪ সেকেন্ড দিয়ে শুরু করুন।
- স্লাইডিং উইন্ডোতে
MEDIA-SEQUENCEশুধু সামনে এগোয়। জানালা ৩০-৬০ সেকেন্ড, আর মুছে ফেলায় একটু দেরি রাখুন। PROGRAM-DATE-TIMEচালু রাখুন আর ঘড়ি NTP-তে মেলান। latency মাপা, ক্লিপ কাটা আর সিঙ্ক এর উপর দাঁড়িয়ে।- ffmpeg বন্ধ হলে
ENDLISTলেখে। লাইভেomit_endlist,discont_startআর epoch থেকে শুরুর নম্বর ব্যবহার করুন। - কতগুলো স্ট্রিম ধরবে সেটা নিজে মাপুন, ৩০-৫০% হেডরুম রেখে; কারো দেওয়া সংখ্যা আপনার কন্টেন্টে খাটবে না।