...
This commit is contained in:
parent
e979b78fb5
commit
ee7cee7638
@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.media.MediaCodec
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
@ -13,7 +14,9 @@ import android.renderscript.RenderScript
|
||||
import android.renderscript.ScriptIntrinsicYuvToRGB
|
||||
import android.renderscript.Type
|
||||
import android.service.wallpaper.WallpaperService
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.WindowManager
|
||||
import bums.lunatic.launcher.R
|
||||
|
||||
|
||||
@ -26,14 +29,33 @@ class MyWallpaperService : WallpaperService() {
|
||||
lateinit var holder: SurfaceHolder
|
||||
private var renderThread: RenderThread? = null
|
||||
|
||||
private var screenWidth: Int = 0
|
||||
private var screenHeight: Int = 0
|
||||
|
||||
override fun onCreate(surfaceHolder: SurfaceHolder) {
|
||||
super.onCreate(surfaceHolder)
|
||||
this.holder = surfaceHolder
|
||||
val wm = getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
||||
val metrics = DisplayMetrics()
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) {
|
||||
// Android 11 이상 권장 방법
|
||||
val display = wm.currentWindowMetrics
|
||||
// Insets(네비게이션바 등 제외 영역 계산 가능), 필요한 경우 처리
|
||||
val bounds = display.bounds
|
||||
screenWidth = bounds.width()
|
||||
screenHeight = bounds.height()
|
||||
} else {
|
||||
// 하위 버전 처리 방법
|
||||
@Suppress("DEPRECATION")
|
||||
wm.defaultDisplay.getMetrics(metrics)
|
||||
screenWidth = metrics.widthPixels
|
||||
screenHeight = metrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceCreated(holder: SurfaceHolder) {
|
||||
super.onSurfaceCreated(holder)
|
||||
renderThread = RenderThread(holder, getApplicationContext())
|
||||
renderThread = RenderThread(holder, getApplicationContext(), screenWidth, screenHeight)
|
||||
renderThread?.start()
|
||||
}
|
||||
|
||||
@ -46,7 +68,9 @@ class MyWallpaperService : WallpaperService() {
|
||||
}
|
||||
class RenderThread(
|
||||
private val holder: SurfaceHolder,
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
private val screenWidth: Int,
|
||||
private val screenHeight: Int
|
||||
) : Thread() {
|
||||
|
||||
private var running = true
|
||||
@ -59,14 +83,28 @@ class RenderThread(
|
||||
private var rs: RenderScript? = null
|
||||
private var yuvToRgb: ScriptIntrinsicYuvToRGB? = null
|
||||
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private var videoWidth = 0
|
||||
private var videoHeight = 0
|
||||
|
||||
// 왕복 이동 관련 변수
|
||||
private var offset = 0f
|
||||
private var direction = 1
|
||||
private val speed = 2.5f // 픽셀/프레임, 효과 속도 조절
|
||||
|
||||
// 비율 맞춤 렌더 사이즈와 경계
|
||||
private var renderWidth = 0f
|
||||
private var renderHeight = 0f
|
||||
private var renderStartX = 0f
|
||||
private var renderStartY = 0f
|
||||
private var maxOffset = 0f
|
||||
private var moveXAxis = false
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
// 1. MediaExtractor 초기화
|
||||
extractor = MediaExtractor().apply {
|
||||
val afd = context.resources.openRawResourceFd(R.raw.sample)
|
||||
// 영상 소스 위치에 따라 아래 setDataSource 예시를 변경하십시오.
|
||||
val afd = context.resources.openRawResourceFd(R.raw.sample) // ex) res/raw/sample.mp4
|
||||
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
|
||||
afd.close()
|
||||
for (i in 0 until trackCount) {
|
||||
@ -75,19 +113,16 @@ class RenderThread(
|
||||
if (mime.startsWith("video/")) {
|
||||
videoTrackIndex = i
|
||||
selectTrack(i)
|
||||
width = format.getInteger(MediaFormat.KEY_WIDTH)
|
||||
height = format.getInteger(MediaFormat.KEY_HEIGHT)
|
||||
videoWidth = format.getInteger(MediaFormat.KEY_WIDTH)
|
||||
videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (videoTrackIndex < 0) {
|
||||
// 비디오 트랙 없음 종료
|
||||
return
|
||||
}
|
||||
if (videoTrackIndex < 0) return
|
||||
|
||||
// 2. MediaCodec 초기화 (Surface가 아닌 직접 버퍼 받을 것)
|
||||
// 2. MediaCodec 초기화
|
||||
extractor?.getTrackFormat(videoTrackIndex)?.let { format ->
|
||||
codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!).apply {
|
||||
configure(format, null, null, 0)
|
||||
@ -99,8 +134,26 @@ class RenderThread(
|
||||
rs = RenderScript.create(context)
|
||||
yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
|
||||
|
||||
// 4. 비율 맞춤 Scaling 및 이동 관련 변수 설정
|
||||
val scale = if (videoWidth > videoHeight) {
|
||||
// 가로가 더 긴 경우: 화면 세로에 맞춤
|
||||
screenHeight.toFloat() / videoHeight
|
||||
} else {
|
||||
// 세로가 더 긴 경우: 화면 가로에 맞춤
|
||||
screenWidth.toFloat() / videoWidth
|
||||
}
|
||||
renderWidth = videoWidth * scale
|
||||
renderHeight = videoHeight * scale
|
||||
renderStartX = (screenWidth - renderWidth) / 2f
|
||||
renderStartY = (screenHeight - renderHeight) / 2f
|
||||
|
||||
moveXAxis = renderWidth > screenWidth
|
||||
maxOffset = if (moveXAxis) renderWidth - screenWidth else if (renderHeight > screenHeight) renderHeight - screenHeight else 0f
|
||||
|
||||
var isEOS = false
|
||||
|
||||
while (running && !isInterrupted) {
|
||||
// 5. MediaCodec 프레임 디코딩
|
||||
if (!isEOS) {
|
||||
val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1
|
||||
if (inputBufferIndex >= 0) {
|
||||
@ -109,16 +162,11 @@ class RenderThread(
|
||||
val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1
|
||||
if (sampleSize < 0) {
|
||||
codec?.queueInputBuffer(
|
||||
inputBufferIndex,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
MediaCodec.BUFFER_FLAG_END_OF_STREAM
|
||||
)
|
||||
inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
|
||||
isEOS = true
|
||||
} else {
|
||||
val presentationTimeUs = extractor?.sampleTime ?: 0L
|
||||
codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0)
|
||||
val pts = extractor?.sampleTime ?: 0L
|
||||
codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, pts, 0)
|
||||
extractor?.advance()
|
||||
}
|
||||
}
|
||||
@ -132,42 +180,51 @@ class RenderThread(
|
||||
val yuvData = ByteArray(bufferInfo.size)
|
||||
outputBuffer.get(yuvData)
|
||||
|
||||
// 4. RenderScript로 YUV → Bitmap 변환
|
||||
val bitmap = convertYUVToBitmap(yuvData, width, height)
|
||||
val bitmap = convertYUVToBitmap(yuvData, videoWidth, videoHeight)
|
||||
|
||||
// 5. Canvas에 그리기
|
||||
// 6. 왕복 이동 애니메이션 offset 계산
|
||||
if (maxOffset > 0f) {
|
||||
offset += direction * speed
|
||||
if (offset < 0f) { offset = 0f; direction = 1 }
|
||||
if (offset > maxOffset) { offset = maxOffset; direction = -1 }
|
||||
}
|
||||
|
||||
// 7. Canvas에 그리고 unlock
|
||||
val canvas = holder.lockCanvas()
|
||||
if (canvas != null) {
|
||||
canvas.drawBitmap(bitmap, 0f, 0f, null)
|
||||
|
||||
// 오버레이 예: 텍스트 그리기
|
||||
val drawX = if (moveXAxis) renderStartX - offset else renderStartX
|
||||
val drawY = if (!moveXAxis) renderStartY - offset else renderStartY
|
||||
val dstRect = RectF(
|
||||
drawX, drawY,
|
||||
drawX + renderWidth, drawY + renderHeight
|
||||
)
|
||||
canvas.drawColor(Color.BLACK)
|
||||
canvas.drawBitmap(bitmap, null, dstRect, null)
|
||||
// 오버레이 예시
|
||||
val paint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
textSize = 40f
|
||||
setShadowLayer(4f, 2f, 2f, Color.DKGRAY)
|
||||
}
|
||||
canvas.drawText("Live Wallpaper Overlay", 50f, 50f, paint)
|
||||
|
||||
canvas.drawText("Live Wallpaper Overlay", 40f, 80f, paint)
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
|
||||
outputBuffer.clear()
|
||||
}
|
||||
codec?.releaseOutputBuffer(outputBufferIndex, false)
|
||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
// format 변경 시 처리할 것 (필요시)
|
||||
// 필요하다면 포맷 변경 처리
|
||||
}
|
||||
|
||||
// 프레임 타이밍 조절 (예: 30fps 제한)
|
||||
sleep(33)
|
||||
sleep(1000L/45L) // 약 30fps
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
// 리소스 해제
|
||||
codec?.stop()
|
||||
codec?.release()
|
||||
try {
|
||||
codec?.stop(); codec?.release()
|
||||
extractor?.release()
|
||||
rs?.destroy()
|
||||
} catch (e: Exception) { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user