EMB visionVISION
데모 바로가기

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 의존성으로 전이 포함됩니다.

@OptIn(UnstableApi::class) — 공개 API 가 Media3 @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 코드를 생성합니다.

ChatGPT Claude Copilot Cursor
# 에이전트에게 그대로 요청
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 PlayerViewAndroidView 로 감싸고, VisionEnhancerplayer 에 붙입니다.

@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 추가). 같은 ExoPlayersetMediaItem 으로 소스를 바꿔도 이펙트는 그대로 유지됩니다.

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초 후 비동기로 검증합니다. 통과하면 그대로 유지, 실패하면 효과를 끄고 원본으로 되돌립니다(결과는 프로세스 내 캐시).

웹과의 핵심 차이 — 네이티브는 도메인(Origin) 대신 앱 packageNameapp 으로 서버에 전송합니다. 라이선스의 allowedOrigins 에 앱의 packageName 이 포함돼야 통과합니다(웹 도메인과 같은 리스트에 함께). localhost 면제 같은 건 없습니다. INTERNET 권한이 필요합니다(라이브러리에 이미 선언됨).
packageName 위변조 방지 — app 식별자는 SDK 가 프레임워크 ActivityThread.currentPackageName()(시스템이 프로세스 bind 시 설정)에서 직접 읽습니다. 전달한 context.packageName 은 폴백일 뿐이라 ContextWrapper 등으로 다른 앱 ID 를 위조할 수 없습니다.
개발 빌드 면제 (웹 localhost 대응) — 디버그 빌드(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) — 적용 불가, 원본 자동 폴백

Widevine 등 DRM 으로 보호된 영상에는 화질 개선을 적용할 수 없습니다. 보호 콘텐츠는 secure surface 라 GL 처리가 불가능하기 때문입니다. 대신 SDK 가 암호화 비디오 트랙(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 측정을 권장합니다.