...
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
package bums.lunatic.launcher.wall
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.media.MediaCodec
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
import android.renderscript.Allocation
|
||||
import android.renderscript.Element
|
||||
import android.renderscript.RenderScript
|
||||
import android.renderscript.ScriptIntrinsicYuvToRGB
|
||||
import android.renderscript.Type
|
||||
import android.service.wallpaper.WallpaperService
|
||||
import android.view.SurfaceHolder
|
||||
import bums.lunatic.launcher.R
|
||||
|
||||
|
||||
class MyWallpaperService : WallpaperService() {
|
||||
override fun onCreateEngine(): Engine {
|
||||
return VideoEngine()
|
||||
}
|
||||
|
||||
inner class VideoEngine : Engine() {
|
||||
lateinit var holder: SurfaceHolder
|
||||
private var renderThread: RenderThread? = null
|
||||
|
||||
override fun onCreate(surfaceHolder: SurfaceHolder) {
|
||||
super.onCreate(surfaceHolder)
|
||||
this.holder = surfaceHolder
|
||||
}
|
||||
|
||||
override fun onSurfaceCreated(holder: SurfaceHolder) {
|
||||
super.onSurfaceCreated(holder)
|
||||
renderThread = RenderThread(holder, getApplicationContext())
|
||||
renderThread?.start()
|
||||
}
|
||||
|
||||
override fun onSurfaceDestroyed(holder: SurfaceHolder?) {
|
||||
super.onSurfaceDestroyed(holder)
|
||||
renderThread?.interrupt()
|
||||
renderThread = null
|
||||
}
|
||||
}
|
||||
}
|
||||
class RenderThread(
|
||||
private val holder: SurfaceHolder,
|
||||
private val context: Context
|
||||
) : Thread() {
|
||||
|
||||
private var running = true
|
||||
|
||||
private var extractor: MediaExtractor? = null
|
||||
private var codec: MediaCodec? = null
|
||||
private var videoTrackIndex: Int = -1
|
||||
private val bufferInfo = MediaCodec.BufferInfo()
|
||||
|
||||
private var rs: RenderScript? = null
|
||||
private var yuvToRgb: ScriptIntrinsicYuvToRGB? = null
|
||||
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
|
||||
override fun run() {
|
||||
try {
|
||||
// 1. MediaExtractor 초기화
|
||||
extractor = MediaExtractor().apply {
|
||||
val afd = context.resources.openRawResourceFd(R.raw.sample)
|
||||
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
|
||||
afd.close()
|
||||
for (i in 0 until trackCount) {
|
||||
val format = getTrackFormat(i)
|
||||
val mime = format.getString(MediaFormat.KEY_MIME) ?: ""
|
||||
if (mime.startsWith("video/")) {
|
||||
videoTrackIndex = i
|
||||
selectTrack(i)
|
||||
width = format.getInteger(MediaFormat.KEY_WIDTH)
|
||||
height = format.getInteger(MediaFormat.KEY_HEIGHT)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (videoTrackIndex < 0) {
|
||||
// 비디오 트랙 없음 종료
|
||||
return
|
||||
}
|
||||
|
||||
// 2. MediaCodec 초기화 (Surface가 아닌 직접 버퍼 받을 것)
|
||||
extractor?.getTrackFormat(videoTrackIndex)?.let { format ->
|
||||
codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME)!!).apply {
|
||||
configure(format, null, null, 0)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
// 3. RenderScript 초기화
|
||||
rs = RenderScript.create(context)
|
||||
yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
|
||||
|
||||
var isEOS = false
|
||||
while (running && !isInterrupted) {
|
||||
if (!isEOS) {
|
||||
val inputBufferIndex = codec?.dequeueInputBuffer(10000) ?: -1
|
||||
if (inputBufferIndex >= 0) {
|
||||
val inputBuffer = codec?.getInputBuffer(inputBufferIndex)
|
||||
if (inputBuffer != null) {
|
||||
val sampleSize = extractor?.readSampleData(inputBuffer, 0) ?: -1
|
||||
if (sampleSize < 0) {
|
||||
codec?.queueInputBuffer(
|
||||
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)
|
||||
extractor?.advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val outputBufferIndex = codec?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1
|
||||
if (outputBufferIndex >= 0) {
|
||||
val outputBuffer = codec?.getOutputBuffer(outputBufferIndex)
|
||||
if (bufferInfo.size > 0 && outputBuffer != null) {
|
||||
val yuvData = ByteArray(bufferInfo.size)
|
||||
outputBuffer.get(yuvData)
|
||||
|
||||
// 4. RenderScript로 YUV → Bitmap 변환
|
||||
val bitmap = convertYUVToBitmap(yuvData, width, height)
|
||||
|
||||
// 5. Canvas에 그리기
|
||||
val canvas = holder.lockCanvas()
|
||||
if (canvas != null) {
|
||||
canvas.drawBitmap(bitmap, 0f, 0f, null)
|
||||
|
||||
// 오버레이 예: 텍스트 그리기
|
||||
val paint = Paint().apply {
|
||||
color = Color.WHITE
|
||||
textSize = 40f
|
||||
}
|
||||
canvas.drawText("Live Wallpaper Overlay", 50f, 50f, paint)
|
||||
|
||||
holder.unlockCanvasAndPost(canvas)
|
||||
}
|
||||
|
||||
outputBuffer.clear()
|
||||
}
|
||||
codec?.releaseOutputBuffer(outputBufferIndex, false)
|
||||
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
|
||||
// format 변경 시 처리할 것 (필요시)
|
||||
}
|
||||
|
||||
// 프레임 타이밍 조절 (예: 30fps 제한)
|
||||
sleep(33)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
// 리소스 해제
|
||||
codec?.stop()
|
||||
codec?.release()
|
||||
extractor?.release()
|
||||
rs?.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertYUVToBitmap(yuvByteArray: ByteArray, width: Int, height: Int): Bitmap {
|
||||
val yuvType = Type.Builder(rs, Element.U8(rs)).setX(yuvByteArray.size)
|
||||
val inAllocation = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT)
|
||||
|
||||
val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height)
|
||||
val outAllocation = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT)
|
||||
|
||||
inAllocation.copyFrom(yuvByteArray)
|
||||
yuvToRgb?.setInput(inAllocation)
|
||||
yuvToRgb?.forEach(outAllocation)
|
||||
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
outAllocation.copyTo(bitmap)
|
||||
|
||||
inAllocation.destroy()
|
||||
outAllocation.destroy()
|
||||
|
||||
return bitmap
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user