...
This commit is contained in:
parent
e979b78fb5
commit
ee7cee7638
@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
import android.media.MediaCodec
|
import android.media.MediaCodec
|
||||||
import android.media.MediaExtractor
|
import android.media.MediaExtractor
|
||||||
import android.media.MediaFormat
|
import android.media.MediaFormat
|
||||||
@ -13,7 +14,9 @@ import android.renderscript.RenderScript
|
|||||||
import android.renderscript.ScriptIntrinsicYuvToRGB
|
import android.renderscript.ScriptIntrinsicYuvToRGB
|
||||||
import android.renderscript.Type
|
import android.renderscript.Type
|
||||||
import android.service.wallpaper.WallpaperService
|
import android.service.wallpaper.WallpaperService
|
||||||
|
import android.util.DisplayMetrics
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
|
import android.view.WindowManager
|
||||||
import bums.lunatic.launcher.R
|
import bums.lunatic.launcher.R
|
||||||
|
|
||||||
|
|
||||||
@ -26,14 +29,33 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
lateinit var holder: SurfaceHolder
|
lateinit var holder: SurfaceHolder
|
||||||
private var renderThread: RenderThread? = null
|
private var renderThread: RenderThread? = null
|
||||||
|
|
||||||
|
private var screenWidth: Int = 0
|
||||||
|
private var screenHeight: Int = 0
|
||||||
|
|
||||||
override fun onCreate(surfaceHolder: SurfaceHolder) {
|
override fun onCreate(surfaceHolder: SurfaceHolder) {
|
||||||
super.onCreate(surfaceHolder)
|
super.onCreate(surfaceHolder)
|
||||||
this.holder = 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) {
|
override fun onSurfaceCreated(holder: SurfaceHolder) {
|
||||||
super.onSurfaceCreated(holder)
|
super.onSurfaceCreated(holder)
|
||||||
renderThread = RenderThread(holder, getApplicationContext())
|
renderThread = RenderThread(holder, getApplicationContext(), screenWidth, screenHeight)
|
||||||
renderThread?.start()
|
renderThread?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +68,9 @@ class MyWallpaperService : WallpaperService() {
|
|||||||
}
|
}
|
||||||
class RenderThread(
|
class RenderThread(
|
||||||
private val holder: SurfaceHolder,
|
private val holder: SurfaceHolder,
|
||||||
private val context: Context
|
private val context: Context,
|
||||||
|
private val screenWidth: Int,
|
||||||
|
private val screenHeight: Int
|
||||||
) : Thread() {
|
) : Thread() {
|
||||||
|
|
||||||
private var running = true
|
private var running = true
|
||||||
@ -59,14 +83,28 @@ class RenderThread(
|
|||||||
private var rs: RenderScript? = null
|
private var rs: RenderScript? = null
|
||||||
private var yuvToRgb: ScriptIntrinsicYuvToRGB? = null
|
private var yuvToRgb: ScriptIntrinsicYuvToRGB? = null
|
||||||
|
|
||||||
private var width = 0
|
private var videoWidth = 0
|
||||||
private var height = 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() {
|
override fun run() {
|
||||||
try {
|
try {
|
||||||
// 1. MediaExtractor 초기화
|
// 1. MediaExtractor 초기화
|
||||||
extractor = MediaExtractor().apply {
|
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)
|
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
|
||||||
afd.close()
|
afd.close()
|
||||||
for (i in 0 until trackCount) {
|
for (i in 0 until trackCount) {
|
||||||
@ -75,19 +113,16 @@ class RenderThread(
|
|||||||
if (mime.startsWith("video/")) {
|
if (mime.startsWith("video/")) {
|
||||||
videoTrackIndex = i
|
videoTrackIndex = i
|
||||||
selectTrack(i)
|
selectTrack(i)
|
||||||
width = format.getInteger(MediaFormat.KEY_WIDTH)
|
videoWidth = format.getInteger(MediaFormat.KEY_WIDTH)
|
||||||
height = format.getInteger(MediaFormat.KEY_HEIGHT)
|
videoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoTrackIndex < 0) {
|
if (videoTrackIndex < 0) return
|
||||||
// 비디오 트랙 없음 종료
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. MediaCodec 초기화 (Surface가 아닌 직접 버퍼 받을 것)
|
// 2. MediaCodec 초기화
|
||||||
extractor?.getTrackFormat(videoTrackIndex)?.let { format ->
|
extractor?.getTrackFormat(videoTrackIndex)?.let { format ->
|
||||||
codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!).apply {
|
codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!).apply {
|
||||||
configure(format, null, null, 0)
|
configure(format, null, null, 0)
|
||||||
@ -99,8 +134,26 @@ class RenderThread(
|
|||||||
rs = RenderScript.create(context)
|
rs = RenderScript.create(context)
|
||||||
yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
|
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
|
var isEOS = false
|
||||||
|
|
||||||
while (running && !isInterrupted) {
|
while (running && !isInterrupted) {
|
||||||
|
// 5. MediaCodec 프레임 디코딩
|
||||||
if (!isEOS) {
|
if (!isEOS) {
|
||||||
val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1
|
val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1
|
||||||
if (inputBufferIndex >= 0) {
|
if (inputBufferIndex >= 0) {
|
||||||
@ -109,16 +162,11 @@ class RenderThread(
|
|||||||
val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1
|
val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1
|
||||||
if (sampleSize < 0) {
|
if (sampleSize < 0) {
|
||||||
codec?.queueInputBuffer(
|
codec?.queueInputBuffer(
|
||||||
inputBufferIndex,
|
inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
MediaCodec.BUFFER_FLAG_END_OF_STREAM
|
|
||||||
)
|
|
||||||
isEOS = true
|
isEOS = true
|
||||||
} else {
|
} else {
|
||||||
val presentationTimeUs = extractor?.sampleTime ?: 0L
|
val pts = extractor?.sampleTime ?: 0L
|
||||||
codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0)
|
codec?.queueInputBuffer(inputBufferIndex, 0, sampleSize, pts, 0)
|
||||||
extractor?.advance()
|
extractor?.advance()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,42 +180,51 @@ class RenderThread(
|
|||||||
val yuvData = ByteArray(bufferInfo.size)
|
val yuvData = ByteArray(bufferInfo.size)
|
||||||
outputBuffer.get(yuvData)
|
outputBuffer.get(yuvData)
|
||||||
|
|
||||||
// 4. RenderScript로 YUV → Bitmap 변환
|
val bitmap = convertYUVToBitmap(yuvData, videoWidth, videoHeight)
|
||||||
val bitmap = convertYUVToBitmap(yuvData, width, height)
|
|
||||||
|
|
||||||
// 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()
|
val canvas = holder.lockCanvas()
|
||||||
if (canvas != null) {
|
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 {
|
val paint = Paint().apply {
|
||||||
color = Color.WHITE
|
color = Color.WHITE
|
||||||
textSize = 40f
|
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)
|
holder.unlockCanvasAndPost(canvas)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputBuffer.clear()
|
outputBuffer.clear()
|
||||||
}
|
}
|
||||||
codec?.releaseOutputBuffer(outputBufferIndex, false)
|
codec?.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||||
// format 변경 시 처리할 것 (필요시)
|
// 필요하다면 포맷 변경 처리
|
||||||
}
|
}
|
||||||
|
sleep(1000L/45L) // 약 30fps
|
||||||
// 프레임 타이밍 조절 (예: 30fps 제한)
|
|
||||||
sleep(33)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
// 리소스 해제
|
try {
|
||||||
codec?.stop()
|
codec?.stop(); codec?.release()
|
||||||
codec?.release()
|
extractor?.release()
|
||||||
extractor?.release()
|
rs?.destroy()
|
||||||
rs?.destroy()
|
} catch (e: Exception) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user