Eğitim Rehberi İleri Seviye 6 Aşama

Headless Chromium & FFmpeg ile Donanım Hızlandırmalı Video Render Rehberi

Web Audio API FFT, Puppeteer Sanal Saat, FFmpeg Stdin Pipe ve Docker ile 60 FPS Deterministik Render

20 Şubat 2026 Sistem & Medya Tamamlandı 35 dk uygulama 8 dk okuma
#FFmpeg #Puppeteer #Web Audio API #DSP #Chromium #Hardware Acceleration #Docker
Zorluk Seviyesi
İleri Seviye
Aşama Sayısı
6 Aşama
Tahmini Süre
35 dk uygulama
Format
Adım Adım Rehber
Gerekli Ön Koşullar & Hazırlık:
Node.js 20+ ve npm paket yöneticisi FFmpeg (H.264 veya NVENC donanım hızlandırma destekli) Web Audio API ve Canvas/WebGL grafik programlama temelleri Docker ve konteynerleştirme mantığı

Headless Chromium & FFmpeg ile Donanım Hızlandırmalı Video Render Rehberi

Müzik prodüksiyonlarında ve ses projelerinde parçaların YouTube, Instagram ve TikTok gibi video platformlarına aktarılması için estetik ses dalga formları (audio visualizer), parçacık efektleri ve frekansa duyarlı spektrum animasyonları sıkça kullanılır. After Effects veya DaVinci Resolve gibi masaüstü yazılımları bu süreçte yüksek lisans bedeli ve yoğun manuel işçilik gerektirirken; web teknolojileri (HTML5 Canvas, WebGL, Web Audio API) ve headless tarayıcılar bu süreci tamamen otomatik ve ölçeklenebilir hale getirir.

Ancak tarayıcıyı açıp gerçek zamanlı ekran kaydı (screen recording) almak; CPU dalgalanmalarında kare düşmelerine (frame drop) ve ses-görüntü desenkronizasyonuna (desync) yol açar.

Bu sorunun kesin mühendislik çözümü: Deterministik Kare Kare (Frame-by-Frame) Render Mimarisidir.

Bu eğitim rehberinde, sıfırdan Web Audio API ile ses frekans analizi yapan, Puppeteer sanal saati ile her 1/60 saniyelik anı dondurup kare yakalayan ve doğrudan FFmpeg stdin pipe hattına basarak ultra akıcı 4K/60 FPS video üreten bir render hattı (pipeline) inşa edeceğiz.


1. Rehber Özeti & Neler Öğreneceğiz?

Bu rehberi tamamladığınızda şu yetkinlikleri kazanmış olacaksınız:

  • Deterministik Zaman Akışı: Tarayıcının requestAnimationFrame döngüsünü sanal adımlarla simüle ederek sıfır kare kaybı sağlama.
  • Dijital Sinyal İşleme (DSP & FFT): OfflineAudioContext ve Fast Fourier Transform ile ses verisini 60 FPS frekans dizilerine dönüştürme.
  • WebGL / Canvas Reaktif Tasarım: Frekans genliklerine göre ölçeklenen dinamik görsel dalga formları üretme.
  • Stdin Pipe Mimarisi: Diske binlerce PNG yazıp I/O darboğazı yaratmak yerine RAM üzerinden doğrudan FFmpeg'e ham RGBA aktarma.
  • Donanım Hızlandırma & Docker: NVENC/VAAPI ve Chromium bayraklarıyla sunucu ortamında 10 kata varan render hızı elde etme.

2. Ön Koşullar & Gerekli Araçlar

Bileşen Minimum Sürüm Amaç
Node.js 20.x LTS Pipeline orkestrasyonu ve dosya akışları
FFmpeg 6.0+ Video kodlama (libx264 veya h264_nvenc)
Puppeteer 22.x+ Headless Chromium kontrolü ve CDP erişimi
Docker & Docker Compose Güncel İzole ve taşınabilir render ortamı

3. Aşama Aşama Adımlar

Adım 1: Deterministik Video Render Mimarisi ve Çerçeve Senkronizasyonu

Sistem gerçek zamanlı oynatma yapmaz. 3 dakikalık bir şarkıyı 60 FPS hızında render etmek için toplam 3 * 60 * 60 = 10.800 adet karenin matematiksel olarak üretilmesi gerekir:

[ Ses Dosyası (.wav / .mp3) ]
             |
             v
[ OfflineAudioContext / FFT Analizi ] ---> JSON Spektrum Verisi (60 FPS Array)
             |
             v
[ Headless Chromium (Puppeteer / CDP) ]
  - Canvas Zaman Çizelgesi Adımlama: currentTime += 1/60
  - Raw RGBA Frame Stream (Page.captureScreenshot veya Screencast)
             |
             v
[ FFmpeg Pipe (stdin) ]
  - Rawvideo -> H.264 / NVENC Donanım Kodlayıcı
  - Orijinal Ses Akışının (AAC) Mux Edilmesi
             |
             v
[ 4K / 60 FPS Ultra Akıcı MP4 Video ]

Bu sayede render sunucusu ister 1 FPS hızında hesaplasın ister 120 FPS hızında, üretilen video kusursuz bir 60 FPS akıcılıkta kalır.


Adım 2: OfflineAudioContext ile DSP Analizi ve FFT Spektrum Verisi Çıkarımı

Ses dosyasının zaman domenindeki (time domain) genlik verisini frekans bileşenlerine (frequency domain) çevirmek için Hızlı Fourier Dönüşümü (FFT) kullanılır.

Node.js ortamında ses dosyasını okuyup her kare için spektrum dizisi üreten extract-spectrum.mjs dosyasını oluşturalım:

// extract-spectrum.mjs
import fs from 'node:fs';
import { AudioContext } from 'web-audio-api';

export async function generateSpectrumFrames(audioFilePath, fps = 60, fftSize = 1024) {
  const fileBuffer = fs.readFileSync(audioFilePath);
  const audioCtx = new AudioContext();

  // 1. Sesi Decode Et
  const audioBuffer = await new Promise((resolve, reject) => {
    audioCtx.decodeAudioData(fileBuffer, resolve, reject);
  });

  const sampleRate = audioBuffer.sampleRate;
  const channelData = audioBuffer.getChannelData(0); // Sol kanal üzerinden analiz
  const totalDuration = audioBuffer.duration;
  const totalFrames = Math.floor(totalDuration * fps);
  const samplesPerFrame = Math.floor(sampleRate / fps);

  const frames = [];

  // 2. Her 1/60 saniye için FFT aralığını hesapla
  for (let f = 0; f < totalFrames; f++) {
    const start = f * samplesPerFrame;
    const windowSlice = channelData.slice(start, start + fftSize);

    // Frekans enerjisini hesapla (Basit 16 bantlık eşitleyici)
    const bands = new Array(16).fill(0);
    const bandStep = Math.floor(windowSlice.length / 16);

    for (let b = 0; b < 16; b++) {
      let sum = 0;
      for (let s = 0; s < bandStep; s++) {
        sum += Math.abs(windowSlice[b * bandStep + s] || 0);
      }
      bands[b] = Math.min(1.0, (sum / bandStep) * 4.0); // Normalize et
    }

    frames.push(bands);
  }

  return { totalFrames, duration: totalDuration, frames };
}

Adım 3: HTML5 Canvas ve WebGL Tabanlı Reaktif Görselleştirici Sayfası

Tarayıcı içinde render edilecek şablon sayfamız template.html:

<!DOCTYPE html>
<html lang="tr">
<head>
  <meta charset="UTF-8">
  <style>
    body {
      margin: 0;
      padding: 0;
      background: #0d0f12;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 1920px;
      height: 1080px;
      overflow: hidden;
    }
    #visualizer {
      width: 1920px;
      height: 1080px;
    }
  </style>
</head>
<body>
  <canvas id="visualizer" width="1920" height="1080"></canvas>
  <script>
    const canvas = document.getElementById('visualizer');
    const ctx = canvas.getContext('2d');
    let currentBands = new Array(16).fill(0);

    // Puppeteer bu fonksiyonu her karede çağırır:
    window.renderFrameAt = function(bands) {
      currentBands = bands;
      ctx.fillStyle = '#0d0f12';
      ctx.fillRect(0, 0, 1920, 1080);

      // Simetrik dalga formu çizimi
      const barWidth = 40;
      const gap = 20;
      const totalWidth = 16 * (barWidth + gap);
      const startX = (1920 - totalWidth) / 2;

      for (let i = 0; i < 16; i++) {
        const height = Math.max(10, currentBands[i] * 500);
        const x = startX + i * (barWidth + gap);
        const y = (1080 - height) / 2;

        const gradient = ctx.createLinearGradient(0, y, 0, y + height);
        gradient.addColorStop(0, '#10b981');
        gradient.addColorStop(1, '#06b6d4');

        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.roundRect(x, y, barWidth, height, 12);
        ctx.fill();
      }
    };
  </script>
</body>
</html>

Adım 4: Puppeteer Sanal Saat ve Kare Kare Ekran Yakalama Mekanizması

Puppeteer başlatılırken donanım hızlandırma ve deterministik saat bayraklarıyla yapılandırılır:

import puppeteer from 'puppeteer';

export async function launchDeterministicBrowser() {
  return await puppeteer.launch({
    headless: 'new',
    args: [
      '--no-sandbox',
      '--disable-setuid-sandbox',
      '--enable-gpu-rasterization',
      '--enable-zero-copy',
      '--ignore-gpu-blocklist',
      '--window-size=1920,1080',
    ],
  });
}

Sayfayı açtıktan sonra döngü içinde window.renderFrameAt(spectrumData) tetiklenir ve ham RGBA buffer'ı alınır:

const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 });
await page.goto(`file://${process.cwd()}/template.html`);

// Her kare için:
await page.evaluate((bands) => window.renderFrameAt(bands), currentSpectrum);
const frameBuffer = await page.screenshot({ type: 'png', omitBackground: false });

Adım 5: Node.js Üzerinden FFmpeg Stdin Pipe ile Donanım Hızlandırmalı Kodlama

Diske dosya yazmak yerine doğrudan Node.js child_process.spawn ile FFmpeg'e pipe açıyoruz:

// render-pipeline.mjs
import { spawn } from 'node:child_process';
import { generateSpectrumFrames } from './extract-spectrum.mjs';
import { launchDeterministicBrowser } from './browser.mjs';

export async function renderVideo(audioPath, outputPath) {
  const { totalFrames, frames } = await generateSpectrumFrames(audioPath, 60);
  const browser = await launchDeterministicBrowser();
  const page = await browser.newPage();
  await page.goto(`file://${process.cwd()}/template.html`);

  // FFmpeg Sürecini Başlat
  const ffmpeg = spawn('ffmpeg', [
    '-y',
    '-f', 'image2pipe',
    '-vcodec', 'png',
    '-r', '60',
    '-i', 'pipe:0',             // Kareler stdin'den gelecek
    '-i', audioPath,            // Orijinal ses dosyası
    '-c:v', 'libx264',          // Donanım için: 'h264_nvenc' veya 'h264_videotoolbox'
    '-preset', 'fast',
    '-crf', '18',
    '-c:a', 'aac',
    '-b:a', '320k',
    '-pix_fmt', 'yuv420p',
    '-shortest',
    outputPath,
  ]);

  ffmpeg.stderr.on('data', (d) => process.stdout.write(d.toString()));

  // Deterministik Kare Döngüsü
  for (let i = 0; i < totalFrames; i++) {
    await page.evaluate((b) => window.renderFrameAt(b), frames[i]);
    const screenshot = await page.screenshot({ type: 'png' });
    ffmpeg.stdin.write(screenshot);

    if (i % 60 === 0) {
      console.log(`Render: %${Math.round((i / totalFrames) * 100)} tamamlandı`);
    }
  }

  ffmpeg.stdin.end();
  await browser.close();
}

Adım 6: Docker Ortamında GPU Passthrough ve Headless Chromium Optimizasyonu

Render işlemini sunucuya taşımak için Dockerfile hazırlıyoruz:

FROM node:20-bookworm-slim

# FFmpeg ve Chromium Bağımlılıkları
RUN apt-get update && apt-get install -y \
    ffmpeg \
    chromium \
    fonts-freefont-ttf \
    libgl1-mesa-glx \
    libegl1-mesa \
    && rm -rf /var/lib/apt/lists/*

ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
CMD ["node", "render-cli.js"]

NVIDIA GPU bulunan bir sunucuda çalıştırmak için docker run komutuna --gpus all bayrağı eklenmelidir.


4. Yaygın Hatalar ve Çözümleri (Troubleshooting)

1. Ses ve Görüntü Arasında Senkron Kayması (Desync)

  • Neden: Kare hesaplamasında sampleRate / fps tam bölünmediğinde zamanla biriken kayma.
  • Çözüm: Kare indeksini Math.floor(f * sampleRate / fps) şeklinde her döngüde mutlak örnek (sample) numarasıyla hesaplayın.

2. Node.js Out of Memory (OOM) Hatası

  • Neden: FFmpeg stdin pipe hattı dolduğunda ffmpeg.stdin.write() arka arkaya çağrılırsa bellek şişer.
  • Çözüm: ffmpeg.stdin.write() false döndüğünde drain olayını bekleyin (await once(ffmpeg.stdin, 'drain')).

3. Chromium Crash (Shared Memory Limit)

  • Neden: Docker konteynerlerinde varsayılan /dev/shm boyutunun 64 MB olması.
  • Çözüm: Docker başlatılırken --shm-size=2gb parametresini verin veya Chromium bayraklarına --disable-dev-shm-usage ekleyin.

5. Test, Doğrulama & Canlı Çalıştırma

Pipeline'ı 5 saniyelik bir test sesiyle doğrulayalım:

# 1. Test sesi üretme (FFmpeg ile 440 Hz sinüs dalgası)
ffmpeg -f lavfi -i "sine=frequency=440:duration=5" -c:a pcm_s16le test-audio.wav

# 2. Render scriptini koşturma
node -e "import('./render-pipeline.mjs').then(m => m.renderVideo('test-audio.wav', 'output.mp4'))"

Video tamamlandığında çıktı dosyasını ffprobe ile doğrulayın:

ffprobe -v error -select_streams v:0 -show_entries stream=width,height,r_frame_rate,duration -of default=noprint_wrappers=1 output.mp4

Beklenen Doğrulama Çıktısı:

width=1920
height=1080
r_frame_rate=60/1
duration=5.000000

Tüm değerler tam eşleştiğinde; donanım hızlandırmalı, sıfır desenkronizasyonlu ve stüdyo kalitesinde video üretim hattınız canlı kullanıma hazırdır!