Android音视频系列

android ffmpeg mp4转gif模糊问题

2021-08-20  本文已影响0人  坑逼的严

最近老是要生成一些gif图片,图片转gif或者是视频转gif,网上的工具软件又要钱,于是自己写了一个,记录一下代码。
https://github.com/microshow/RxFFmpeg
这是一个优秀的开源ffmpeg库,可自己输入命令执行,还有进度返回,以及停止命令操作。平台架构全,支持 armeabi-v7a, arm64-v8a, x86, x86_64。一般我们用到armeabi-v7a, arm64-v8a就行了,要不是小米应用市场年底明确要求要64位架构,也懒得引用这么多。

视频转gif

1629446394831.gif

一般我们的命令就是直接将mp4转gif,如下:

val text =
     "ffmpeg -y -ss ${mMinPercentage} -t ${mMaxPercentage-mMinPercentage} -i ${resultPhotos!!.get(0).path} -r 15 -preset superfast /storage/emulated/0/1.gif"
val commands = text.split(" ").toTypedArray()
开始执行FFmpeg命令
RxFFmpegInvoke.getInstance()
      .runCommandRxJava(commands)
      .subscribe(MyRxFFmpegSubscriber(this@VideoToGifActivity))

${mMinPercentage} -t ${mMaxPercentage-mMinPercentage}

是时间设置比如:3 -t 4 就是从第三秒开始,往后延续4秒也就是3秒到7秒之间。这个要理解

-i ${resultPhotos!!.get(0).path}

指的是视频地址,这里要视频全路径,比如:/storage/emulated/0/1.mp4

-r 15

是指15帧

-preset superfast

是设置ffmpeg执行速度还能设置 ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow, placebo,速度越慢,越高质量。
但是这样生成出来的gif是很模糊的。

加入调色板
public void gotoPalettegen(){
  val text =
            "ffmpeg -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} -r ${mFrame} -vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen -y /storage/emulated/0/JJGif/pt.png"
        Log.d("yanjin","text= ${text}")
        val commands = text.split(" ").toTypedArray()
        //开始执行FFmpeg命令
        RxFFmpegInvoke.getInstance()
            .runCommandRxJava(commands)
            .subscribe(object : RxFFmpegSubscriber() {
                override fun onError(message: String?) {
                    if (showLoading != null) {
                        showLoading!!.dismissAllowingStateLoss()
                    }
                }

                override fun onFinish() {
                      goToGif()
                }

                override fun onProgress(p: Int, progressTime: Long) {}
                override fun onCancel() {
                    if (showLoading != null) {
                        showLoading!!.dismissAllowingStateLoss()
                    }
                }
            })
}

public void gotoGif(){
  val text =
            "ffmpeg -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} -r ${mFrame} -vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen -y /storage/emulated/0/JJGif/pt.png"
        Log.d("yanjin","text= ${text}")
        val commands = text.split(" ").toTypedArray()
        //开始执行FFmpeg命令
        RxFFmpegInvoke.getInstance()
            .runCommandRxJava(commands)
            .subscribe(object : RxFFmpegSubscriber() {
                override fun onError(message: String?) {
                    if (showLoading != null) {
                        showLoading!!.dismissAllowingStateLoss()
                    }
                }

                override fun onFinish() {
                    val text =
                        "ffmpeg -v warning -ss ${mMinPercentage} -t ${mMaxPercentage - mMinPercentage} -i ${resultPhotos!!.get(0).path} " +
                                "-i /storage/emulated/0/JJGif/pt.png -r ${mFrame} " +
                                "-lavfi fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos[x];[x][1:v]paletteuse -y /storage/emulated/0/JJGif/${resultPhotos!!.get(0).name}.gif"
                    Log.d("yanjin", "text= ${text}")
                    val commands = text.split(" ").toTypedArray()
                    //开始执行FFmpeg命令
                    RxFFmpegInvoke.getInstance()
                        .runCommandRxJava(commands)
                        .subscribe(MyRxFFmpegSubscriber(this@VideoToGifActivity))
                }

                override fun onProgress(p: Int, progressTime: Long) {}
                override fun onCancel() {
                    if (showLoading != null) {
                        showLoading!!.dismissAllowingStateLoss()
                    }
                }
            })
}

class MyRxFFmpegSubscriber(activity: VideoToGifActivity):RxFFmpegSubscriber() {
        private var weakReference:WeakReference<VideoToGifActivity>? = null
        init {
            weakReference = WeakReference(activity)
        }
        override fun onError(message: String?) {
            if(weakReference == null || weakReference!!.get() == null){
                return
            }
            if (weakReference!!.get()!!.showLoading != null) {
                weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
            }
        }

        override fun onFinish() {
            if(weakReference == null || weakReference!!.get() == null){
                return
            }
            weakReference!!.get()!!.mTvNext?.postDelayed(object : Runnable {
                override fun run() {
                    Toast.makeText(App.getInstance(), "已保存gif到${weakReference!!.get()!!.path}", Toast.LENGTH_LONG)
                    if (weakReference!!.get()!!.showLoading != null) {
                        weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
                    }
                    weakReference!!.get()!!.gotoFinishActivity()
                }

            }, 1000)
        }

        override fun onProgress(progress: Int, progressTime: Long) {
            if(weakReference == null || weakReference!!.get() == null){
                return
            }
            var totalProgressTime = (weakReference!!.get()!!.mMaxPercentage - weakReference!!.get()!!.mMinPercentage) * 1000000
            if (weakReference!!.get()!!.showLoading != null) {
                var progress: Float =
                    (progressTime.toFloat() / totalProgressTime.toFloat()).toFloat()
                if (progress > 0.95f) {
                    weakReference!!.get()!!.showLoading!!.onProgressChanged(1.0f)
                } else {
                    weakReference!!.get()!!.showLoading!!.onProgressChanged(progress)
                }
            }
        }

        override fun onCancel() {
            if(weakReference == null || weakReference!!.get() == null){
                return
            }
            if (weakReference!!.get()!!.showLoading != null) {
                weakReference!!.get()!!.showLoading!!.dismissAllowingStateLoss()
            }
        }

    }

我们在执行每一个视频转gif的时候,都要先生成这个视频的调色图片也就是gotoPalettegen方法。
我们分析下他的命令

${mMinPercentage} -t ${mMaxPercentage - mMinPercentage}

老样子,还是时间设置

-i ${resultPhotos!!.get(0).path}

还是视频地址

-r ${mFrame}

还是帧率

-vf fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos,palettegen

这个就是生成调色板的图。值得一提的是设置宽高,就是如下这段代码:

scale=${mCurrentWidth}:${mCurrentHeight}:

如果我们写生固定数字,比如:scale=500:500: 那么他就生成是500*500的图片,如果视屏比例不是1:1,那么就会产生压缩,如果不想被压缩,那么ffmpeg还支持宽度固定,高度自适应比如:scale=360:-1: 把高度设置为-1时,就能设置这个功能了。

-y /storage/emulated/0/JJGif/pt.png

就是调色板存放路径了,作为一个程序员,尤其是android程序员,权限问题一定不能忘记。
然后在完成后再执行真正的生成gif代码也就是gotoGif方法
老样子,我们看命令
老代码就不说了,说说不同点

-i /storage/emulated/0/JJGif/pt.png 

这就是刚刚生成的调色板图片,我们引入他
然后设置全局应用

-lavfi fps=${mFrame},scale=${mCurrentWidth}:${mCurrentHeight}:flags=lanczos[x];[x][1:v]paletteuse

当然还有局部应用,大家可以去网上查查。

-y /storage/emulated/0/JJGif/${resultPhotos!!.get(0).name}.gif

这个就是生成gif了。
代码麻烦了一点,多了层嵌套。

图片转gif

我们可以用ffmpeg,命令简单,但是我这里再介绍一个好用的开源库Gifflen
https://github.com/lchad/Gifflen-Android

var list: ArrayList<File> = ArrayList()
        var mGiffle = Gifflen.Builder()
            .color(0)
            .delay(mCurrentSpeed) //每相邻两帧之间播放的时间间隔.
            .quality(mCurrentQuality)
            .width(mCurrentWidth) //生成Gif文件的宽度(像素).
            .height(mCurrentHeight) //生成Gif文件的高度(像素).
            .bgColor(mCurrentColor)
            .listener(object : Gifflen.OnEncodeFinishListener {
                override fun onEncodeFinish(path: String?) {
                    Toast.makeText(App.getInstance(), "已保存gif到$path", Toast.LENGTH_LONG)
                        .show()
                    if(showLoading != null){
                        showLoading!!.dismissAllowingStateLoss()
                    }
                    dismissAllowingStateLoss()
                    gotoFinishActivity(path)
                }

                override fun onEncodeProgress(progress: Float) {
                    if(showLoading != null){
                        showLoading!!.onProgressChanged(progress)
                    }
                }

            })
            .build()
        resultPhotos?.forEach {
            list.add(File(it.path))
        }
        //线程池异步调用
        ExecutorsTools.getInstance().addTask(RunnableImp(mGiffle,list))
上一篇下一篇

猜你喜欢

热点阅读