EMBRACE Vision SDK — Android
ExoPlayer(Media3) 가 재생하는 영상 프레임을
OpenGL ES 로 실시간 개선하는 네이티브 SDK입니다. Media3
Effect 파이프라인에 한 번 붙이면 적용되며, 웹·iOS SDK
와 동일한 옵션·프로필·라이선스 계약을 공유합니다. minSdk 24.
licenseKey 를 지정하면
효과가 즉시 적용되고 2초 후 검증되며, 실패 시 원본으로 되돌립니다.
네이티브는 도메인 대신 앱 packageName 으로 검증합니다(아래
라이선스). 키 발급 문의:
support@sgrsoft.com.
소개 #
재생은 당신의 ExoPlayer 가, 화면 개선은 이 SDK
가
담당합니다. 영상을 로드·디코드·재생하지 않으며, Media3
Effect 로 등록되는
GlEffect → GLSL ES 패스 체인 으로 프레임의 출력
화면만 개선합니다. HLS·DASH 등 ExoPlayer 가 재생하는 어떤 소스와도
함께 쓸 수 있습니다.
-
명령형
VisionEnhancer(권장) + 뷰 호스트VisionPlayerView - 9종 프로필 + 업스케일(FSR/Lanczos) + 엣지 강조 + CNN(ES 3.1 compute)
- 웹·iOS SDK 와 1:1 미러 — 같은 프로필·옵션·라이선스 키 체계
- Kotlin · Jetpack Compose 호환, AAR(raw Maven) 바이너리 배포
설치 (Maven · AAR) #
AAR 은 인증 토큰이 필요 없는 raw GitHub Maven 저장소로 배포합니다. 저장소와 좌표만 추가하면 됩니다.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google(); mavenCentral()
maven { url = uri("https://raw.githubusercontent.com/SGRsoft-Dev/vision-android-sdk/main") }
}
}
// app/build.gradle.kts
dependencies {
implementation("com.sgrsoft.vision:vision:0.1.9")
}
요구 사항: minSdk 24 · compileSdk 34 · JDK 17 · Kotlin.
Media3 (media3-common/exoplayer/effect/ui
1.4.1)는 api 의존성으로 전이 포함됩니다.
@UnstableApi 표면(Effect/ExoPlayer)을 노출하므로,
VisionEnhancer/VisionPlayerView 를
호출하는 함수·클래스에 @OptIn(UnstableApi::class) 를
붙여야 합니다.
빠른 시작 #
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.sgrsoft.vision.VisionEnhancer
import com.sgrsoft.vision.VisionOptions
import com.sgrsoft.vision.VisionProfile
@OptIn(UnstableApi::class)
fun setup(context: Context, url: String) {
val player = ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(url)); prepare(); playWhenReady = true // 재생은 사용자가 제어
}
val view = PlayerView(context).apply { this.player = player }
val enhancer = VisionEnhancer(
player = player,
options = VisionOptions(profile = VisionProfile.default, licenseKey = "{licenseKey}"),
context = context, // packageName 으로 라이선스 검증
onLicense = { state -> } // pending → ok / invalid
)
}
별도 렌더 루프나 SurfaceView 구성이 필요 없습니다.
{licenseKey} 에 발급받은 키를 넣으면 효과가 바로
시작되고 2초 후 검증됩니다(실패 시 원본으로 되돌림).
비율·레이아웃은 호출측 책임입니다.
licenseKey = "VSK-ZP02Z-K3FJ9-4C4DQ-8EM6A"
체험 키는 발급된 packageName 에만 바인딩됩니다. 본인 앱에서 쓰려면 앱의 applicationId(packageName)를 등록한 키를 support@sgrsoft.com 으로 발급받으세요(웹의 localhost 면제 같은 건 네이티브엔 없습니다).
LLM 코드 생성 #
코딩 에이전트가 Android SDK 를 정확히 사용하도록 돕는 압축
레퍼런스 android-llms.txt 를 제공합니다. 읽히면
옵션·프로필·라이선스(packageName)·제약을 한 번에 이해하고 Kotlin
코드를 생성합니다.
# 에이전트에게 그대로 요청
https://emb-vision-demo.web.app/android-llms.txt 읽고 ExoPlayer 적용 예제코드 알려줘
JavaScript · /llms.txt ↗ iOS · /ios-llms.txt ↗ Android · /android-llms.txt ↗
기본 사용법 #
명령형 컨트롤러 VisionEnhancer 로 옵션을
갱신/토글하고 상태를 조회합니다(웹 enhance() 미러).
val enhancer = VisionEnhancer(player, VisionOptions(profile = VisionProfile.ott), context)
enhancer.update(VisionOptions(profile = VisionProfile.cctv, contrast = 0.4)) // 옵션 갱신
enhancer.setEnabled(false) // 잠깐 끄기(원본)
val state = enhancer.getState() // VisionState
enhancer.destroy()
update(...) 는 이펙트를
재생성하지 않고 공유 state 를 갱신하므로 프로필·강도를 즉시
반영합니다(뷰 재구성 불필요). 라이선스 결과도 통과 시 자동
반영됩니다.
프로필 #
상황별 권장 프리셋입니다. 개별 강도 옵션을 함께 지정하면 프로필 기본값을 덮어씁니다. (웹·iOS와 동일한 9종)
| profile | 용도 |
|---|---|
VisionProfile.default |
Cinematic — 자연스럽고 즉시 체감되는 실사용 메인 |
.detail |
Ultra Detail — 시연/마케팅용 와우 효과 |
.ott |
저비트레이트 스트리밍 압축 노이즈 완화 |
.education |
텍스트/판서 가독성 강조 |
.cctv |
야간/저조도 대비 강조 |
.anime |
라인 강조 + 색면 평탄화 |
.oled |
깊은 black + 영화적 대비 |
.retro |
CRT scanline + warm gamma |
.off |
효과 비활성(원본 출력) |
업스케일 #
저해상도 영상을 출력 해상도로 SR 처리합니다.
UpscaleParams 로 원하는 조합을 직접 구성합니다.
// 권장 조합: 1080p + FSR EASU
VisionOptions(
profile = VisionProfile.ott,
upscale = UpscaleParams(enabled = true, algorithm = UpscaleAlgorithm.fsr,
target = UpscaleTarget.p1080, sharpness = 0.06)
)
| 용도 | UpscaleParams (예시) |
|---|---|
| 비활성 | upscale 생략(null) |
| 안정 | lanczos · p1080 · 0.04 |
| 고품질 | fsr · p1080 · 0.06 |
| 강조 | fsr · p1080 · 0.10 |
| 저전력 | bilinear · p720 · 0.03 |
엣지 강조 #
unsharp(classic)과 RCAS(halo-free)를 하나의 API로 통합했습니다.
VisionOptions(
profile = VisionProfile.default,
edgeEnhancement = EdgeEnhancementParams(enabled = true,
algorithm = EdgeAlgorithm.rcas, strength = 0.5)
)
Compose / View #
Jetpack Compose
Media3 PlayerView 를 AndroidView 로
감싸고, VisionEnhancer 를 player 에
붙입니다.
@OptIn(UnstableApi::class)
@Composable
fun Player(player: ExoPlayer) {
val ctx = LocalContext.current
DisposableEffect(player) {
val enhancer = VisionEnhancer(player, VisionOptions(profile = VisionProfile.default), ctx)
onDispose { enhancer.destroy() }
}
AndroidView(factory = { PlayerView(it).apply { this.player = player } })
}
View 시스템 — VisionPlayerView
VisionPlayerView(FrameLayout) 는 내부에
Media3 PlayerView 를 두고 한 번에 처리합니다.
val v = VisionPlayerView(context)
v.setPlayer(player, VisionOptions(profile = VisionProfile.ott), onLicense = { s -> })
v.update(VisionOptions(profile = VisionProfile.oled)) // 라이브 갱신
v.release()
ExoPlayer · 소스 전환 #
HLS·DASH 는 ExoPlayer 가 기본 지원합니다(HLS 는
media3-exoplayer-hls 추가). 같은
ExoPlayer 에 setMediaItem 으로 소스를
바꿔도 이펙트는 그대로 유지됩니다.
val player = ExoPlayer.Builder(context).build()
// VisionEnhancer(player, options, context) 부착 후
player.setMediaItem(MediaItem.fromUri(newURL)); player.prepare() // 끊김 없이 전환
player 에 등록돼 있으므로 소스를 바꿔도 다시
부착할 필요가 없습니다.
전체화면 #
전체화면·회전·컨트롤 UI 는 호출측에서 구성합니다(SDK 는 프레임만
그립니다). 예제 앱 example/android 에 Compose
전체화면 + 가로 회전 + 좌상단 화질개선 토글 + 다크모드 패턴이
구현돼 있습니다.
// 단일 PlayerView 를 일반/전체화면 레이아웃 사이에서 이동
AndroidView(factory = {
(playerView.parent as? ViewGroup)?.removeView(playerView); playerView
}, modifier = if (fullscreen) Modifier.fillMaxSize() else Modifier.aspectRatio(16f/9f))
API · VisionEnhancer #
VisionEnhancer(player: ExoPlayer,
options: VisionOptions,
context: Context? = null,
deviceInfo: DeviceInfo = DeviceInfo.default(),
onLicense: ((LicenseState) -> Unit)? = null)
| 멤버 | 설명 |
|---|---|
init(player, options, context?, …) |
ExoPlayer 에 GL 이펙트 부착(웹
enhance() 미러). context 는
라이선스 검증의 packageName 출처.
|
update(options) |
옵션 라이브 갱신(이펙트 재생성 없음) |
setEnabled(Boolean) |
효과 on/off 단축 |
getState() |
VisionState { enabled, profile, rendererKind:"gles",
license, uniforms }
|
licenseState |
현재 LicenseState |
destroy() |
이펙트 제거(player.setVideoEffects(empty)) |
API · VisionPlayerView #
FrameLayout 서브클래스. 내부
playerView(Media3 PlayerView)를 노출합니다.
| 멤버 | 설명 |
|---|---|
setPlayer(player, options, onLicense?) |
ExoPlayer 연결 + 개선 활성(내부에서 VisionEnhancer 생성) |
update(options) |
개선 옵션 라이브 갱신 |
playerView |
내부 Media3 PlayerView(컨트롤·리사이즈 등
직접 제어)
|
release() |
이펙트 해제 + player 연결 해제 |
API · VisionOptions #
웹 VideoEnhancementOptions / iOS
VisionOptions 와 동일 필드명·의미. 개별 강도는 프로필
기본값을 덮어씁니다.
| 필드 | 타입 / 범위 | 설명 |
|---|---|---|
enabled |
Boolean | 효과 on/off (기본 true) |
profile |
VisionProfile? | 프로필 프리셋(.default 등) |
sharpen contrast
cleanup anime
|
Double? 0..1 | 개별 강도 |
gamma |
Double? 0.5..2.0 | 감마 |
localContrast darkLift |
Double? 0..1 | 로컬 대비 / 암부 lift |
scanline |
ScanlineParams? | 스캔라인(enabled/intensity/spacing) |
upscale |
UpscaleParams? | 업스케일(algorithm/target/sharpness) |
edgeEnhancement |
EdgeEnhancementParams? | 엣지 강조 통합 API(off/classic/rcas) |
cnn1 cnn |
Cnn1Params? / CnnParams? | CNN 복원(ES 3.1 compute) |
renderer |
VisionRenderer? | auto / gles / vulkan (현재 gles 단일) |
autoReduceOnLowEnd |
Boolean? | 저사양 자동 감쇠(기본 true) |
licenseKey |
String? | 지정 시 효과 즉시 적용→2초 후 검증, 실패 시 OFF(원본) |
업스케일 enum — UpscaleAlgorithm:
none/nearest/bilinear/lanczos/fsr ·
UpscaleTarget:
source/p720/p1080/p1440/x2/x3/custom.
API · 타입 · Exports #
// 뷰/컨트롤러
VisionEnhancer, VisionPlayerView, VisionGlEffect // (Media3 GlEffect)
// 옵션/타입
VisionOptions, VisionProfile, VisionRenderer, UpscaleAlgorithm, UpscaleTarget,
EdgeAlgorithm, ScanlineParams, UpscaleParams, EdgeEnhancementParams,
Cnn1Params, CnnParams, CnnLayer, EnhancementUniforms, VisionState, LicenseState
// 네임스페이스
VisionSdk.version // "0.1.9" (SDK 버전)
VisionSdk.specVersion // 프로필 스펙 버전
VisionSdk.profiles // 프로필 목록
라이선스 (packageName 바인딩) #
낙관적 게이팅 — licenseKey 지정 시 효과가
즉시 적용되고, 2초 후 비동기로 검증합니다. 통과하면
그대로 유지, 실패하면 효과를 끄고
원본으로 되돌립니다(결과는 프로세스 내 캐시).
app 으로 서버에 전송합니다.
라이선스의 allowedOrigins 에 앱의
packageName 이 포함돼야 통과합니다(웹 도메인과 같은
리스트에 함께). localhost 면제 같은 건 없습니다.
INTERNET 권한이 필요합니다(라이브러리에 이미 선언됨).
ActivityThread.currentPackageName()(시스템이 프로세스
bind 시 설정)에서 직접 읽습니다. 전달한
context.packageName 은 폴백일 뿐이라
ContextWrapper 등으로 다른 앱 ID 를 위조할 수
없습니다.
FLAG_DEBUGGABLE)는 라이선스 검증을 건너뛰고 바로
구동됩니다. 릴리스 APK 만 검증되며(Play 가 debuggable
릴리스 업로드를 거부), 출시 빌드는 면제 신호를 위조할 수 없습니다.
상태 조회:
VisionEnhancer(player,
VisionOptions(profile = VisionProfile.default, licenseKey = key),
context,
onLicense = { state -> /* none/pending/ok/invalid */ })
enhancer.getState().license // LicenseState
본인 앱은 applicationId(packageName)를 등록한 키를 support@sgrsoft.com 으로 발급받으세요. 검증 서버 주소는 SDK 에 고정 내장됩니다.
CNN 화질 복원 #
외부 가중치(81 floats/layer)를 주입해 multi-layer CNN 으로 디테일을 복원합니다. Android 는 GL 컨텍스트가 ES 3.1 compute 를 지원할 때만 동작하며, 미지원 환경에서는 자동 생략합니다(웹 WebGL 폴백과 동일 — 크래시 없음).
VisionOptions(
profile = VisionProfile.default,
cnn = CnnParams(enabled = true, layers = layers) // List<CnnLayer>
)
OpenGL ES 백엔드 #
SDK 는 Media3
Effect(GlEffect/BaseGlShaderProgram) 로
동작합니다. 프레임마다 검증된 GLSL ES 패스 체인(combined → upscale(lanczos/FSR EASU) → RCAS → CNN)을 실행합니다.
- combined/upscale/RCAS 패스는 ES 2.0+ 에서 동작합니다.
- CNN 패스만 ES 3.1 compute 가 필요하며, 미지원이면 graceful skip 됩니다.
-
renderer는 현재gles단일 백엔드입니다(Vulkan 은 로드맵).
제약 · 한계 #
이 SDK 는 ExoPlayer 프레임을 GL 텍스처로 처리합니다.
프레임을 읽을 수 없는 환경에는 적용되지 않으므로 도입 전 아래
제약을 확인하세요.
DRM(Widevine) — 적용 불가, 원본 자동 폴백
Format.drmInitData)을 감지하면 이펙트를 떼어 빈 화면 없이 원본을 그대로 재생합니다(onTracksChanged 기준 자동 attach/detach).
클리어 소스로 바뀌면 자동 재부착합니다.
적용 가능: DRM 미적용(클리어) HLS·DASH·프로그레시브 스트림, 무료/프리뷰·광고 등 보호되지 않은 영상.
성능 · 환경
-
처리량은 기기 GPU 에 의존합니다. 저사양 기기는
autoReduceOnLowEnd(기본 true)로 강도를 자동 감쇠하지만 GPU·발열·배터리는 0 이 아닙니다. - CNN 패스는 ES 3.1 compute 미지원 기기에서 자동 생략됩니다(다른 패스는 정상 동작).
-
공개 API 는 Media3
@UnstableApi를 노출하므로 호출부에@OptIn(UnstableApi::class)가 필요합니다. - 체감 화질·FPS·전력은 콘텐츠·코덱·기기에 따라 다르므로 도입 전 실기기 PoC 측정을 권장합니다.