Selaa lähdekoodia

Improve performance of SuperMovieMaker

cooperku_kdanmobile 6 vuotta sitten
vanhempi
commit
67d2b0d682
1 muutettua tiedostoa jossa 127 lisäystä ja 89 poistoa
  1. 127 89
      src/main/java/com/bomostory/sceneeditmodule/SuperMovieMaker.kt

+ 127 - 89
src/main/java/com/bomostory/sceneeditmodule/SuperMovieMaker.kt

@@ -1,25 +1,27 @@
 package com.bomostory.sceneeditmodule
 
 import android.content.Context
+import android.graphics.Bitmap
 import android.media.MediaMetadataRetriever
 import android.util.Log
 import com.bomostory.sceneeditmodule.screen.movie.MovieEditActivity.Companion.FPS
 import com.bomostory.sceneeditmodule.basicdata.Music
 import com.bomostory.sceneeditmodule.basicdata.Project
+import com.bomostory.sceneeditmodule.screen.movie.MediaCodecMovieEncoder
+import com.bomostory.sceneeditmodule.screen.movie.MovieEncoder
 import com.bomostory.sceneeditmodule.utils.FileUtils
 import com.example.exportmedia.MediaHelper
 import com.example.exportmedia.audio.AudioConcat
 import com.example.exportmedia.audio.AudioLooper
 import com.example.exportmedia.data.AudioSource
-import com.example.exportmedia.vedio.MovieMaker
-import io.reactivex.Completable
+import io.reactivex.*
 import io.reactivex.Observable
 import io.reactivex.schedulers.Schedulers
+import pl.droidsonroids.gif.GifDrawable
 import java.io.File
+import java.util.*
 import java.util.concurrent.Semaphore
 import kotlin.collections.ArrayList
-import kotlin.collections.LinkedHashMap
-import kotlin.collections.set
 
 class SuperMovieMaker {
 
@@ -30,58 +32,83 @@ class SuperMovieMaker {
             musics: List<Music>,
             scaleWidth: Int,
             scaleHeight: Int,
-            mediaHelper: MediaHelper): Observable<String> {
+            mediaHelper: MediaHelper,
+            movieEncoder: MovieEncoder? = MediaCodecMovieEncoder()): Observable<String> {
         return Observable.create<String> { emitter ->
+
             try {
+//                movieEncoder = MediaCodecMovieEncoder()
+//                movieEncoder = JcodecMovieEncoder()
+                movieEncoder?.setVideoDimension(scaleWidth, scaleHeight)
+                movieEncoder?.prepare(outputFile, FPS)
+
                 val audioSources = ArrayList<AudioSource>()
                 project.story?.let {
                     audioSources.add(generateRecordSource(project, mediaHelper))
                 }
                 audioSources.addAll(generateAudioSource(project, musics, mediaHelper))
 
-                val movieBuilder = MovieMaker.Builder(mediaHelper)
-                movieBuilder.fps = FPS
-                movieBuilder.audioSources = audioSources
-
-                val inputSceneStatus = LinkedHashMap<Int, Int>()
                 val frameDataList = generateFrameDataList(project)
-                var bitmapIndex = 0
-                val permitCount = frameDataList.size
-                val semaphore = Semaphore(permitCount)
+                Log.d(this::class.java.simpleName, "frameDataList.size = ${frameDataList.size}")
+                val semaphore = Semaphore(5)
+                var generateCount = 0
                 var completeCount = 0
-                emitter.onNext("$completeCount/${frameDataList.size}")
-
-                var beginTime = System.currentTimeMillis()
-                frameDataList.forEach { frameData ->
-                    val i = bitmapIndex++
-                    inputSceneStatus[i] = frameData.repeat
-                    semaphore.acquire()
-                    Completable.create {
-                        project?.story?.apply {
-                            val scene = scenes[frameData.sceneIndex]
-                            val bitmap = SceneDrawer.drawScene(context, scene, frameData.x, scaleWidth, scaleHeight)
-                            movieBuilder.addImage(i, bitmap!!).blockingSubscribe()
-                        }
-                        it.onComplete()
+//                emitter.onNext("$completeCount/${frameDataList.size}")
+                emitter.onNext("Ready")
+
+                val beginTime = System.nanoTime()
+                var timeDraw = 0L
+                var timeEncode = 0L
+
+                SceneDrawer.reset()
+                movieEncoder?.start()
+                val upstream = Flowable.create<Pair<Bitmap, FrameData>> ({
+                    frameDataList.forEach { frameData ->
+                        semaphore.acquire()
+                        val scene = project.story!!.scenes[frameData.sceneIndex]
+                        val time = System.nanoTime()
+                        val bitmap = SceneDrawer.drawScene(context, scene, frameData.x, scaleWidth, scaleHeight, frameData.millisecond)
+                        timeDraw += System.nanoTime() - time
+                        generateCount++
+                        it.onNext(Pair(bitmap!!, frameData))
                     }
-                            .subscribeOn(Schedulers.computation())
-                            .subscribe {
-                                completeCount++
-                                emitter.onNext("$completeCount/${frameDataList.size}")
-                                semaphore.release()
-                            }
-                }
-                semaphore.acquire(permitCount)
-                var endTime = System.currentTimeMillis()
-                Log.d(this@SuperMovieMaker::class.java.simpleName, "draw + save duration = ${endTime - beginTime}")
-                movieBuilder.inputFilePath = movieBuilder.addInputFile(inputSceneStatus).blockingFirst()
-                emitter.onNext("Encoding video")
-                movieBuilder.build().output(outputFile).blockingSubscribe()
+                    it.onComplete()
+                }, BackpressureStrategy.BUFFER)
+
+                val disposable = upstream
+                        .observeOn(Schedulers.newThread())
+                        .subscribe ({
+                            val bitmap = it.first
+                            val frameData = it.second
+                            val time = System.nanoTime()
+                            movieEncoder?.addFrame(bitmap!!, frameData.repeat)
+                            timeEncode += System.nanoTime() - time
+                            bitmap.recycle()
+
+                            completeCount++
+//                            emitter.onNext(String.format("%3.2f%% (%03d / %03d)", completeCount * 100.0 / frameDataList.size, completeCount, frameDataList.size))
+                            emitter.onNext(String.format("%3.2f%%", completeCount * 100.0 / frameDataList.size))
+//                            emitter.onNext(String.format("%3.2f%% (%03d / %03d)", completeCount * 100.0 / frameDataList.size, completeCount, generateCount))
+                            semaphore.release()
+                        }, {
+                            it.printStackTrace()
+                        })
+                SceneDrawer.reset()
+                movieEncoder?.finish()
+
+                val endTime = System.nanoTime()
+                Log.d(this::class.java.simpleName, "duration = ${(endTime - beginTime) / 1000000000.0}")
+                Log.d(this::class.java.simpleName, "timeDraw = ${timeDraw / 1000000000.0}")
+                Log.d(this::class.java.simpleName, "timeEncode = ${timeEncode / 1000000000.0}")
+                emitter.onComplete()
             } catch (e: Exception) {
                 e.printStackTrace()
             }
-            emitter.onComplete()
         }
+                .doOnDispose {
+                    SceneDrawer.reset()
+                    movieEncoder?.finish()
+                }
     }
 
     private fun generateRecordSource(project: Project, mediaHelper: MediaHelper): AudioSource {
@@ -101,7 +128,7 @@ class SuperMovieMaker {
         }
 
         val audioConcat = audioConcatBuilder.build()
-            audioConcat.output(outputFile).blockingFirst()
+        audioConcat.output(outputFile).blockingFirst()
 
         val mediaMetadataRetriever = MediaMetadataRetriever()
         mediaMetadataRetriever.setDataSource(outputFile.path)
@@ -145,72 +172,83 @@ class SuperMovieMaker {
         return audioSources
     }
 
-    fun generateFrameDataList(project: Project): List<FrameData> {
+    private fun generateFrameDataList(project: Project): List<FrameData> {
         val frameDataList = ArrayList<FrameData>()
         var index = 0
         project.story?.scenes?.forEach { scene ->
             val sceneIndex = index++
+
             scene.record?.apply {
+
+                frameDataList.add(FrameData(sceneIndex, 0, 1L, 0L))
+
                 var x = 0
                 var trackPosition = -1
 
+                val timeSet = TreeSet<Long>()
+                for (layer in scene.layers) {
+                    for (actor in layer.actors) {
+                        if (actor.resourcePath.toLowerCase().endsWith(".gif")) {
+                            val gifDrawable = GifDrawable(actor.resourcePath)
+                            val numberOfFrames = gifDrawable.numberOfFrames
+                            val loopCount = if (gifDrawable.loopCount == 0) Int.MAX_VALUE else gifDrawable.loopCount
+                            val durationCount = Math.ceil(period / gifDrawable.duration.toDouble()).toInt()
+                            var duration = 0L
+                            for (times in 1..Math.min(loopCount, durationCount)) {
+                                for (index in 0 until numberOfFrames) {
+                                    duration += gifDrawable.getFrameDuration(index)
+                                    if (duration > period)
+                                        break
+                                    timeSet.add(duration)
+                                }
+                                if (duration > period)
+                                    break
+                            }
+                        }
+                    }
+                }
+
+                val timeArray = timeSet.toArray() as Array<Any>
+                var timeArrayIndex = 0
+
                 for (t in 0..period step ((1f / FPS) * 1000).toLong()) {
-                    for (track in tracks) {
-                        if (t >= track.time && tracks.indexOf(track) > trackPosition) {
-                            x = track.positionX
-                            trackPosition = tracks.indexOf(track)
+
+                    //  gif animation
+                    var gifUpdate = false
+                    if (timeArrayIndex < timeArray.size) {
+                        var gifTime = timeArray[timeArrayIndex] as Long
+                        if (t >= gifTime) {
+                            while (timeArrayIndex < timeArray.size && t > timeArray[timeArrayIndex] as Long) {
+                                timeArrayIndex++
+                            }
+                            gifUpdate = true
                         }
                     }
 
-                    if (frameDataList.isEmpty()) {
-                        frameDataList.add(FrameData(sceneIndex, x, 1))
-                    } else {
-                        val frameData = frameDataList.last()
-                        if (frameData.x == x) {
-                            frameData.repeat++
+                    var tmpTrackPosition = trackPosition
+                    for (i in (trackPosition + 1) until tracks.size) {
+                        val track = tracks[i]
+                        if (t >= track.time) {
+                            x = track.positionX
+                            tmpTrackPosition = i
                         } else {
-                            frameDataList.add(FrameData(sceneIndex, x, 1))
+                            break
                         }
                     }
+                    trackPosition = tmpTrackPosition
+
+                    val frameData = frameDataList.last()
+                    if (!gifUpdate && frameData.x == x) {
+                        frameData.repeat++
+                    } else {
+                        frameDataList.add(FrameData(sceneIndex, x, 1L, t))
+                    }
                 }
             }
         }
+
         return frameDataList
     }
 
-//    private fun generateMovieFilms(
-//            scene: Scene,
-//            scaleWidth: Int,
-//            scaleHeight: Int): Observable<AbstractMap.SimpleEntry<Int, Bitmap>> {
-//        var preBitmap: Bitmap?
-//        return Observable.create<AbstractMap.SimpleEntry<Int, Bitmap>> {
-//            scene?.apply {
-//                record?.apply {
-//                    var bitmap = SceneDrawer.drawScene(scene, 0, scaleWidth, scaleHeight)
-//                    var bitmapIndex = 0
-//                    var trackPosition = -1
-//
-//                    for (t in 0..period step ((1f / FPS) * 1000).toLong()) {
-//                        for (track in tracks) {
-//                            if (t >= track.time && tracks.indexOf(track) > trackPosition) {
-//                                preBitmap = bitmap
-//                                preBitmap?.recycle()
-//
-//                                bitmap = SceneDrawer.drawScene(scene, track.positionX, scaleWidth, scaleHeight)
-//                                trackPosition = tracks.indexOf(track)
-//                            }
-//                        }
-//
-//                        bitmap?.apply {
-//                            it.onNext(AbstractMap.SimpleEntry(bitmapIndex++, this))
-//                        }
-//                    }
-//                    it.onComplete()
-//                }
-//            }
-//        }
-//    }
-
-
-    data class FrameData(val sceneIndex: Int, val x: Int, var repeat: Int)
+    data class FrameData(val sceneIndex: Int, val x: Int, var repeat: Long, val millisecond: Long)
 }