android ffmpeg mp4转gif模糊问题

最近老是要生成一些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))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,192评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,858评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,517评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,148评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,162评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,905评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,537评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,439评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,956评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,083评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,218评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,899评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,565评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,093评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,201评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,539评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,215评论 2 358

推荐阅读更多精彩内容

  • 不好意思,这里先安利下自己最近上架的应用,因为这个 App 使用了本篇文章中提到的技术方案进行了实现,实际效果欢迎...
    石先阅读 20,536评论 16 25
  • FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。它提供了录制、转换以及流化音...
    丶墨墨丶阅读 15,838评论 9 48
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,739评论 2 59
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,538评论 28 53
  • 人工智能是什么?什么是人工智能?人工智能是未来发展的必然趋势吗?以后人工智能技术真的能达到电影里机器人的智能水平吗...
    ZLLZ阅读 3,783评论 0 5