MicrocosmWorksInovasi dan Arsitektur Kosmos Digital
TentangKontak
MicrocosmWorksInovasi dan Arsitektur Digital Cosmos

Menyediakan solusi IT yang penting. Kami bersemangat tentang teknologi, keamanan, dan membantu bisnis tumbuh melalui infrastruktur IT yang andal dan inovatif.

[email protected]
+91 7011868196
New Delhi, India

Pusat Pertumbuhan AI

AI HubInovasi StartupAkselerator Perusahaan

Solusi

Semua SolusiAplikasi Kesehatan & KebugaranPlatform Video AIPengembangan Agen AI

Sumber Daya

WawasanPanduan IndustriCetak Biru Kasus PenggunaanPola ArsitekturStudi Kasus

Perusahaan

Tentang KamiKontakPekerjaan Kami

Layanan

Konsultasi DigitalInfrastruktur CloudPengembangan SaaSPengembangan AITeknologi Video
Pengembangan ERPKustomisasi ZohoPengembangan OdooIntegrasi SalesforcePengembangan CRM Kustom
Integrasi QuickBooksSolusi IoTPengembangan Blockchain
Konsultasi Keamanan SiberDukungan IT - L3

© 2026 MicrocosmWorks. Semua hak dilindungi.

Kebijakan PrivasiSyarat Layanan
Kembali ke Wawasan
IoT Development

Reframing Video 360 di Flutter: Proyeksi Sferis, Keyframe Quaternion, dan Ekspor GPU

Reframing rekaman equirectangular 360 di Flutter menggunakan proyeksi sferis, keyframe quaternion, dan ekspor yang dipercepat GPU.

Saurav Kumar Gupta's image Saurav Kumar Gupta
•
July 10, 2026
•
Diperbarui July 24, 2026
•
5 min read
Developer using Flutter to edit and reframe a 360° video with spherical projection.webp
5 min read

Reframing Video 360° di Flutter: Proyeksi Sferis, Keyframe Quaternion, dan Ekspor GPU

Bagaimana kami mengubah rekaman mentah equirectangular 360° menjadi video datar yang mulus dan berbingkai — seluruhnya di dalam aplikasi Flutter.

Masalah yang tidak pernah diperingatkan kepada Anda

Video normal memiliki satu hal yang jelas untuk ditunjukkan kepada Anda: bingkai yang diarahkan kamera. Video 360° tidak memiliki hal seperti itu. Kamera merekam semuanya — sebuah bola piksel penuh yang disimpan sebagai persegi panjang equirectangular datar (gambar 2:1 di mana sumbu X adalah bujur dan sumbu Y adalah lintang). Sebelum Anda dapat mengunggah rekaman tersebut ke Instagram, YouTube, atau layar ponsel, seseorang harus menjawab pertanyaan yang sengaja tidak dijawab oleh kamera:

Ke mana penonton harus melihat, dan kapan?

"Reframing" adalah tindakan menerbangkan kamera virtual melalui bola tersebut seiring waktu — panning, tilting, zooming — dan merender video datar normal 16:9 (atau 9:16, atau 2.35:1). Ini adalah fitur yang menjadi dasar dibangunnya GoPro Player, Insta360 Studio, dan plugin GoPro VR Reframe di Adobe Premiere.

Kami membangunnya seluruhnya di Flutter. Postingan ini adalah tur bagian-bagian yang benar-benar sulit: perhitungan proyeksi, interpolasi bebas gimbal-lock, batas ±180°, dan pipeline ekspor GPU yang harus bertahan pada dekoder Android yang sesungguhnya.

Alur end-to-end:

tangkap/impor video 360
  → transcode proxy pratinjau ringan
  → pengguna mengetuk / menggambar / mendeteksi objek secara otomatis
  → pelacakan objek menghasilkan jalur
  → menghasilkan + menghaluskan keyframe
  → SLERP-interpolate orientasi kamera per bingkai (pratinjau langsung)
  → ekspor GPU native ke video datar
  → FFmpeg post-pass (moov atom + perbaikan sinkronisasi A/V)

Sekilas Arsitektur

Kami menjaga sistem dalam tiga lapisan yang bersih sehingga perhitungan dapat diuji secara terpisah dari Flutter:

LapisanLokasiTanggung Jawab
Modelslib/data/models/reframe/SphericalCoordinates/Quaternion, CameraOrientation/MotionConstraints, ReframeKeyframe, ReframeProject
Serviceslib/services/reframe/SphericalProjection (ray casting), CoordinateConverter, KeyframeInterpolator/Generator/Smoother, ReframeExportService
Presentationlib/presentation/.../reframe/Riverpod ReframeNotifier, the native OpenGL Spherical360Player, bbox-drawing overlay, keyframe timeline

Aturan emas: semua trigonometri adalah murni Dart tanpa impor Flutter. Ini berarti setiap fungsi proyeksi dan interpolasi dapat diuji unit tanpa pohon widget, dan perhitungan yang sama menggerakkan pratinjau langsung dan ekspor.

1. Intinya: equirectangular ⟷ sferis

Semuanya dimulai dengan satu pemetaan. Sebuah frame equirectangular hanyalah sebuah persegi panjang di mana:

  • X (0 → lebar) menyapu bujur (yaw) dari −180° hingga +180°
  • Y (0 → tinggi) menyapu lintang (pitch) dari +90° (atas/zenith) hingga −90° (bawah/nadir)

SphericalCoordinates sengaja dibuat kecil — hanya yaw dan pitch. Roll, FOV, dan zoom adalah milik kamera, bukan arah. Berikut adalah konversi yang menjadi dasar keseluruhan fitur:

factory SphericalCoordinates.fromEquirectangular({
  required double x, required double y,
  required double videoWidth, required double videoHeight,
}) {
  final normalizedX = x / videoWidth;
  final normalizedY = y / videoHeight;
  // X maps to yaw: 0->-180°, 0.5->0°, 1.0->180°
  final yaw = (normalizedX - 0.5) * 360.0;
  // Y maps to pitch: 0->90° (top), 0.5->0° (horizon), 1.0->-90° (bottom)
  final pitch = (0.5 - normalizedY) * 180.0;
  return SphericalCoordinates(
    yaw: yaw.clamp(-180.0, 180.0)
    pitch: pitch.clamp(-90.0, 90.0)
  );
}

Inversnya adalah citra cermin (normalizedX = yaw/360 + 0.5), dan kami menggunakannya terus-menerus untuk menggambar bounding box yang dilacak kembali ke pemain.

"Ketuk apa yang Anda lihat" — perspective ray casting

Pengguna tidak berinteraksi dengan persegi panjang equirectangular. Mereka berinteraksi dengan tampilan perspektif yang dirender. Jadi, ketika mereka mengetuk suatu titik pada pemain, kita perlu melemparkan sinar melalui kamera lubang jarum virtual dan menemukan arah mana pada bola yang mereka pukul. Itulah SphericalProjection.screenToSpherical:

final ndcX = (2.0 * screenPoint.dx / playerSize.width) - 1.0;
final ndcY = 1.0 - (2.0 * screenPoint.dy / playerSize.height);
final aspectRatio = playerSize.width / playerSize.height;
final tanHalfFov = math.tan(cameraFov * math.pi / 360.0); // half-angle
double rayX = ndcX * aspectRatio * tanHalfFov;
double rayY = ndcY * tanHalfFov;
double rayZ = 1.0;
// ...normalize, rotate the ray by pitch (around X) then yaw (around Y)...
final sphericalYaw   = math.atan2(finalRayX, finalRayZ) * 180.0 / math.pi;
final sphericalPitch = math.asin(finalRayY.clamp(-1.0, 1.0)) * 180.0 / math.pi;

Penggunaan .clamp(-1.0, 1.0) sebelum asin terlihat paranoid, tetapi floating-point drift akan memberi Anda 1.0000001 dan asin akan mengembalikan NaN — dan satu NaN akan meracuni setiap frame downstream. Clamping defensif di sekitar inverse trig adalah tema yang berulang dalam codebase ini.

2. Gerakan kamera yang halus: quaternion, bukan sudut Euler

Pendekatan naif adalah menyimpan yaw/pitch di setiap keyframe dan menginterpolasi angka secara linear. Hasilnya terlihat buruk. Menginterpolasi sudut Euler menyebabkan gimbal lock, kecepatan sudut yang tidak seragam, dan gerakan patah-patah di dekat kutub.

Kami menginterpolasi orientasi sebagai quaternion menggunakan SLERP (interpolasi linear sferis). Setiap arah dikonversi menjadi quaternion melalui konvensi ZYX Euler:

Quaternion toQuaternion() {
  final yawRad = yaw * math.pi / 180.0;
  final pitchRad = pitch * math.pi / 180.0;
  final cy = math.cos(yawRad * 0.5); final sy = math.sin(yawRad * 0.5);
  final cp = math.cos(pitchRad * 0.5); final sp = math.sin(pitchRad * 0.5);
  return Quaternion(w: cy * cp, x: cy * sp, y: sy * cp, z: -sy * sp);
}

Dan SLERP itu sendiri memiliki dua katup pengaman tingkat produksi yang dibutuhkan setiap implementasi:

static Quaternion slerp(Quaternion a, Quaternion b, double t) {
  a = a.normalize(); b = b.normalize();
  var dotProduct = a.dot(b);
  if (dotProduct < 0.0) { b = -b; dotProduct = -dotProduct; }   // take the shortest hemisphere
  if (dotProduct > 0.9995) {                                     // near-parallel -> fall back to LERP
    return Quaternion(w: a.w + t*(b.w-a.w), x: a.x + t*(b
                      y: a.y + t*(b.y-a.y), z: a.z + t*(b.z-a.z)).normalize();
  }
  final theta0 = math.acos(dotProduct);
  final theta = theta0 * t;
  final sinTheta = math.sin(theta);
  final sinTheta0 = math.sin(theta0);
  final s0 = math.cos(theta) - dotProduct * sinTheta / sinTheta0;
  final s1 = sinTheta / sinTheta0;
  return Quaternion(w: s0*a.w + s1*b.w, /* ...*/).normalize();
}

Dua detail yang tidak dapat dinegosiasikan:

  1. Hemisfer terpendek — q dan −q mewakili orientasi yang sama. Jika dot product negatif, Anda meniadakan satu input atau kamera Anda akan mengambil jalur yang lebih panjang di sekitar bola.
  2. Fallback LERP near-parallel — ketika dua keyframe hampir identik, sinTheta0 → 0 dan Anda membagi dengan nilai yang mendekati nol. Menggunakan kembali LERP biasa di atas dot > 0.9995 menghindari pembengkakan nilai.

Keputusan desain yang halus namun penting: kami melakukan SLERP pada arah, tetapi LERP pada FOV/zoom dan angle-LERP pada roll. Interpolasi sferis adalah alat yang tepat untuk arah; zoom hanyalah skalar dan harus bergerak secara linear:

final qInterp = Quaternion.slerp(from.direction.toQuaternion(), to.direction.toQuaternion(), t);
final interpDirection = SphericalCoordinates.fromQuaternion(qInterp).normalize();
final interpFov  = _lerp(from.fov, to.fov, t);
final interpRoll = _lerpAngle(from.roll, to.roll, t); // wraps across ±180
final interpZoom = _lerp(from.zoom, to.zoom, t);

💡 Saat mengkonversi kembali dari quaternion ke yaw/pitch, kami menggunakan atan2 daripada asin "untuk stabilitas numerik (menghindari masalah domain asin)." Setelah SLERP, quaternion bisa sedikit denormalized, dan atan2 berfungsi dengan baik di mana asin akan menghasilkan NaN.

Easing: dibuat sendiri, pada transisi masuk

Setiap keyframe memiliki kurva easing untuk transisi masuk ke dalamnya. Kurva-kurva tersebut ditulis secara manual sehingga identik di setiap platform:

case KeyframeEasing.linear:    return t;
case KeyframeEasing.easeInOut: return t * t * (3 - 2 * t); // smoothstep S-curve
case KeyframeEasing.easeIn:    return t * t;               // quadratic
case KeyframeEasing.easeOut:   return 1 - (1 - t) * (1 - t);

3. Batas ±180°: bug yang menghantui setiap fitur 360

Inilah jebakannya. Yaw berada di sebuah lingkaran: +179° dan −179° berjarak 2°, bukan 358°. Begitu subjek Anda berjalan melintasi bagian belakang bola, perhitungan naif akan menggerakkan kamera separuh putaran dunia. Masalah tunggal ini muncul di empat lapisan sistem yang berbeda, dan masing-masing membutuhkan perbaikan sendiri.

Lapisan 1 — primitif. Dua pembantu kecil yang diandalkan seluruh codebase:

static double normalizeYaw(double yaw) {
  while (yaw > 180) { yaw -= 360; }
  while (yaw < -180) { yaw += 360; }
  return yaw;
}

// "CRITICAL for seam-safe tracking ... avoids 180° snaps"
static double shortestYawDelta(double from, double to) {
  double delta = to - from;
  if (delta > 180) delta -= 360;
  if (delta < -180) delta += 360;
  return delta;
}

Lapisan 2 — pembuatan keyframe mengakumulasi yaw berkelanjutan (tidak terbatas) sehingga SLERP selalu menginterpolasi jalur terpendek. Alih-alih membatasi setiap sampel hingga ±180°, kami menjumlahkan delta terpendek dan membiarkan nilai berjalan melampaui 180° dengan sengaja:

final normalizedContinuousYaw = SphericalCoordinates.normalizeYaw(continuousYaw);
final yawDelta = SphericalCoordinates.shortestYawDelta(normalizedContinuousYaw, rawCoords.yaw);
continuousYaw = continuousYaw + yawDelta;            // may exceed ±180 on purpose
coords = SphericalCoordinates(yaw: continuousYaw, pitch: rawCoords.pitch);

Lapisan 3 — rekonstruksi bounding-box. Ketika pengguna menggambar kotak yang melintasi batas, keempat sudut melaporkan yaw seperti +170°, +175°, −175°, −170°. SphericalProjection mendeteksi ini (sudut mana pun > 90° dan sudut mana pun < −90°) dan membangun kembali bbox "seam-safe" continuous-yaw dengan menggeser nilai negatif sebesar +360° sebelum mengukur rentangnya.

Lapisan 4 — live player menghaluskan ke arah targetnya menggunakan shortestYawDelta sehingga menyeret melintasi batas tidak pernah tersentak.

Jika Anda mengingat satu hal tentang membangun alat 360: batas bukanlah kasus tepi, melainkan perhatian lintas-sektor. Anggarkan untuk itu di mana pun yaw muncul.

4. Membuatnya terasa seperti Insta360/GoPro

Interpolasi yang halus memberi Anda kamera yang secara teknis benar. Namun itu tidak memberi Anda kamera yang bagus. Tiga bagian "rekayasa rasa" mengisi celah tersebut.

Motion constraints — batasi kecepatan dan akselerasi. Data pelacakan mentah sering goyah. Kami melewatkan keyframe melalui MotionConstraints, yang membatasi seberapa cepat dan seberapa tiba-tiba kamera virtual dapat bergerak (misalnya maxYawSpeed = 120°/s, maxPitchSpeed = 90°/s, maxAcceleration = 180°/s²), dengan preset cinematic(), responsive(), dan production(). Ini adalah perbedaan antara "kamera keamanan melacak lalat" dan "operator dengan tangan yang mantap."

Adaptive FOV — zoom agar sesuai dengan subjek. Kami mengukur ukuran sudut subjek dalam ruang equirectangular dan memilih FOV agar memenuhi fraksi target dari frame:

final angularWidth  = (bboxWidth  / videoWidth)  * 360.0;
final angularHeight = (bboxHeight / videoHeight) * 180.0;
final objectAngularSize = math.max(angularWidth, angularHeight);
final idealFov = objectAngularSize / targetFillRatio; // targetFillRatio ≈ 0.4
return idealFov.clamp(minFov, maxFov);                 // 45°–120°

Motion lead + face→body blend. Kami mendorong framing ~15% dari lebar bbox ke depan searah gerakan, sehingga subjek tidak terpaku pada tepi yang mereka tuju. Dan ketika deteksi wajah hilang, kami tidak langsung melompat ke pusat tubuh — kami melakukan lerp dari pusat wajah terakhir yang diketahui ke pusat tubuh selama ~10 frame.

5. Trik dua video: preview proxy vs. original resolusi penuh

Ini adalah keputusan arsitektur yang membuat keseluruhan fitur dapat digunakan pada ponsel sungguhan.

File 360° GoPro/Insta360 seringkali berupa 4096×2048 HEVC. Dekoder Android kelas bawah kesulitan memutar ulang video tersebut dengan mulus, apalagi sambil menjalankan pelacakan objek di atasnya. Jadi, kami menjalankan pelacakan dan pratinjau langsung pada transcoded proxy (~1440×720, libx264 -preset veryfast -crf 28), dan menyimpan original resolusi penuh hanya untuk ekspor akhir.

// "more reliable than MediaMetadataRetriever for 360° videos
//  which often use unusual codecs (HEVC, high-res)"
final maxSafeWidth = 2048, maxSafeHeight = 1080;
// 2:1 4K (4096×2048) is unsafe -> trigger transcodeForPlayback()

Perangkap yang harus Anda tangani: bounding box keyframe dihitung dalam koordinat proxy dan harus diskalakan ke koordinat asli sebelum ekspor. Jika penskalaan ini salah, frame ekspor Anda akan menunjukkan wilayah yang sedikit berbeda dari yang dipratinjau pengguna. (Kami juga menggunakan ffprobe, bukan MediaMetadataRetriever, untuk metadata dan ekstraksi frame pertama — retriever bawaan Android sering bermasalah dengan codec tidak biasa yang dihasilkan kamera 360.)

6. Ekspor GPU: OpenGL + MediaCodec melalui MethodChannel

Memproyeksikan ulang setiap frame bola 4K melalui filter v360 FFmpeg pada CPU ponsel terlalu lambat. (Kami memang menyimpan string v360 — itu mendokumentasikan semantik proyeksi dengan rapi, v360=e:flat:... yang berarti "equirectangular masuk, datar keluar" — tetapi itu bukan jalur langsung.)

Ekspor yang sesungguhnya menyerahkan keyframe ke renderer native OpenGL ES + MediaCodec melalui MethodChannel:

final result = await _methodChannel.invokeMethod('exportVideoGPU', {
  'videoPath': resolvedVideoPath,
  'outputPath': outputPath,
  'keyframes': keyframesList,
  'initialOrientation': initialOrientationMap,
  'width': settings.exportWidth,
  'height': settings.exportHeight,
  'fps': settings.outputFps.toInt(),
  'includeAudio': settings.includeAudio,
  if (isTrimmed) 'trimStartMs': trimStartMs,
  if (isTrimmed) 'trimEndMs': trimEndMs
});

Kami membatasi ini di balik pemeriksaan kapabilitas (Android API 21+, encoder hardware H.264, OpenGL ES 3.0) dan mengalirkan progres kembali melalui EventChannel. Beberapa hal yang sulit didapat:

  1. Offset yaw −90°. Konvensi Dart kami menempatkan yaw = 0 di tengah gambar equirectangular (u = 0.5); renderer OpenGL menempatkan yaw = 0 di u = 0.75. Jadi, setiap yaw keyframe dipetakan ulang sebesar −90° (dan dibungkus ulang menjadi ±180°) saat melintasi saluran — diterapkan secara simetris ke saluran ekspor dan saluran live-player, sehingga pratinjau dan ekspor sesuai.
  2. Jumlah keyframe yang merosot. Nol keyframe → mensintesis dua yang identik dari orientasi awal (ekspor statis). Satu keyframe → menduplikasinya. Renderer selalu menginginkan setidaknya awal dan akhir.
  3. Trimming mengubah basis timestamp. Ekspor yang dipangkas menggeser setiap keyframe sebesar t − trimStartMs.

FFmpeg post-pass yang tidak ada yang menduga

Anda mungkin mengira ekspor GPU adalah garis finis. Bukan — karena MediaMuxer Android menulis moov atom di akhir file dan dapat membawa offset PTS audio sumber. Hasilnya: ExoPlayer (dan video_player Flutter) membekukan gambar sementara waktu terus berjalan. Jadi setelah proses GPU, FFmpeg melakukan pembersihan yang cepat dan lossless:

// copy video (no re-encode -> zero quality loss), re-encode audio to fix PTS,
// move moov atom to the front for instant playback.
-c:v copy -af "aresample=async=1:first_pts=0" -movflags +faststart

Dan sebuah gotcha kecil yang brutal menjaganya: kami mencari trek audio terlebih dahulu, karena meneruskan flag audio ke video yang tidak memiliki trek audio akan membuat FFmpeg menggantung selamanya di beberapa perangkat. Seluruh post-pass dibungkus dalam batas waktu 60 detik dengan pembatalan saat batas waktu tercapai.

7. Pratinjau langsung juga merupakan native player

Pratinjau di editor bukanlah Flutter CustomPaint — melainkan permukaan OpenGL native yang disematkan melalui AndroidView (dengan viewType seperti 'your_app/spherical360player'), dengan saluran metode/event per tampilan sendiri. Dart Spherical360PlayerController mencerminkan yaw/pitch/roll/fov dan menerapkan offset −90° yang sama sebelum mengirim.

Detail "feel" yang membuatnya menyenangkan:

  1. Penghalusan yang independen frame-rate — faktor = 1 - exp(-speed * dt) sehingga easing terlihat sama pada 60fps dan 90fps, dengan yaw jalur terpendek seam-safe yang sudah terpasang.
  2. Momentum/inersia setelah menggeser, dengan peluruhan berbasis gesekan — dinonaktifkan selama pemutaran, karena selama pemutaran kamera harus mematuhi keyframe, bukan geseran terakhir pengguna.
  3. Satu GestureDetector yang onScaleUpdate-nya melakukan pan dan pinch-zoom bersamaan; double-tap mengaktifkan play/pause. FOV membatasi hingga 30°–120°, pitch hingga ±90°, pada batas kontroler.

Satu perbaikan UX kecil namun signifikan: sebelum beralih dari "tracking" ke "generating keyframes," kami menyisipkan penundaan 120 ms agar Flutter benar-benar menampilkan "100%." Tanpa itu, frame progres akhir dan perubahan status akan masuk dalam batch microtask yang sama dan pengguna tidak akan pernah melihat penyelesaian.

Mesin keadaan, singkatnya

Semua ini dikoordinasikan oleh Riverpod ReframeNotifier yang menggerakkan enum status:

idle → loadingVideo → transcoding
    → (detecting | selectingObject | drawingBboxOnPlayer | autoDetectingOnPlayer)
    → tracking → generatingKeyframes → editing → exporting → completed | error

Beberapa titik masuk memberi makan pelacak yang sama: tap-to-detect (YOLO overlay), bbox manual, atau gestur draw-directly-on-the-player ala Insta360. Semuanya bertemu pada KeyframeGenerator.production().generate(...) → KeyframeSmoother.smooth(...), dan dari sana interpolator menggerakkan setiap frame yang dipratinjau dan diekspor.

Pelajaran yang didapat

  1. Jaga agar perhitungan tetap murni. Tidak ada impor Flutter di lapisan proyeksi/interpolasi. Ini adalah satu-satunya alasan mengapa ini dapat diuji.
  2. Quaternion SLERP untuk arah; LERP untuk skalar. Jangan menginterpolasi sudut Euler, dan jangan melakukan SLERP pada zoom Anda.
  3. Batas ±180° adalah masalah lintas-sektoral, bukan kasus tepi. Ini muncul kembali di primitif, generasi, proyeksi, dan pemain.
  4. Selaraskan konvensi Anda sekali, secara simetris. Offset yaw −90° antara Dart dan OpenGL diterapkan pada kedua batas saluran sehingga pratinjau == ekspor.
  5. Proxy untuk interaksi, original untuk output. Ingatlah untuk menskalakan koordinat Anda di antara keduanya.
  6. "Selesai rendering" ≠ "bermain dengan benar." Anggarkan untuk FFmpeg post-pass untuk memperbaiki penempatan moov atom dan PTS audio, dan periksa sebelum Anda menambahkan flag audio.
  7. Rasa adalah sebuah fitur. Motion constraints, adaptive FOV, dan motion-lead adalah yang membedakan "reframed secara teknis" dari "terlihat seperti pro yang memotretnya."

Hasilnya: editor reframe 360° yang sepenuhnya on-device — ketuk subjek, biarkan melacak, sesuaikan keyframe, dan ekspor video datar yang bersih — dibangun di Flutter dengan lapisan GPU native tipis yang hanya melakukan pekerjaan yang tidak bisa dilakukan Dart dengan cukup cepat.

Flutter360VideoOpenGLQuaternions
Saurav Kumar Gupta's image

Tentang Penulis

Saurav Kumar Gupta

AI & Cloud Solutions Expert at MicrocosmWorks

Building innovative AI-powered solutions and helping businesses transform through cutting-edge technology.

Ingin mempelajari lebih lanjut?

Hubungi kami untuk mendiskusikan bagaimana kami dapat membantu mengimplementasikan solusi ini untuk bisnis Anda.

Hubungi Kami

Pertanyaan yang Sering Diajukan

Accurate reframing requires handling spherical projection, seamless rotation across the ±180° boundary, smooth camera interpolation, and synchronized GPU rendering to produce stable, cinematic video output.

Comments (0)

Share your thoughts and join the conversation

Leave a Comment

Your email will not be published

No comments yet

Be the first to share your thoughts!