diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4228ea01..38a19d85 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -100,6 +100,18 @@ + + + + + + + diff --git a/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt new file mode 100644 index 00000000..270370ac --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/wall/MyWallpaperService.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/res/raw/sample.mp4 b/app/src/main/res/raw/sample.mp4 new file mode 100644 index 00000000..f9f3c6c3 Binary files /dev/null and b/app/src/main/res/raw/sample.mp4 differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa88d4a1..9d7c59d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -178,4 +178,5 @@ 차량 블루투스 이름 + test\n diff --git a/app/src/main/res/xml/wallpaper.xml b/app/src/main/res/xml/wallpaper.xml new file mode 100644 index 00000000..337e2739 --- /dev/null +++ b/app/src/main/res/xml/wallpaper.xml @@ -0,0 +1,6 @@ + +