Android快速视频解码抽帧FFmpegMediaMetadataRetriever,Kotlin(2)
Android MediaMetadataRetriever取视频封面,Kotlin(1)-CSDN博客 使用Android原生的MediaMetadataRetriever耗时太长,第三方的 https://github.com/wseemann/FFmpegMediaMetadataRetriever 可实现快速视频抽帧解码。
添加依赖:
implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever-core:1.0.19'
implementation 'com.github.wseemann:FFmpegMediaMetadataRetriever-native:1.0.19'
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="1px">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="180px"
android:background="@android:color/darker_gray"
android:scaleType="centerCrop" />
</LinearLayout>
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "fly"
const val SPAN_COUNT = 8
const val VIDEO = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rv = findViewById<RecyclerView>(R.id.rv)
val layoutManager = GridLayoutManager(this, SPAN_COUNT)
layoutManager.orientation = GridLayoutManager.VERTICAL
rv.layoutManager = layoutManager
val adapter = MyAdapter(this)
rv.adapter = adapter
rv.layoutManager = layoutManager
val ctx = this
lifecycleScope.launch(Dispatchers.IO) {
val videoList = readAllVideo(ctx)
Log.d(TAG, "readAllVideo size=${videoList.size}")
val lists = arrayListOf<MyData>()
lists.addAll(videoList)
lifecycleScope.launch(Dispatchers.Main) {
adapter.dataChanged(lists)
}
}
}
private fun readAllVideo(ctx: Context): ArrayList<MyData> {
val videos = ArrayList<MyData>()
//读取视频Video
val cursor = ctx.contentResolver.query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
null,
null,
null,
null
)
while (cursor!!.moveToNext()) {
//路径
val path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA))
val id = cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID)
val videoUri: Uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getLong(id))
//名称
//val name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME))
//大小
//val size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE))
videos.add(MyData(videoUri, path, VIDEO))
}
cursor.close()
return videos
}
}
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.collection.LruCache
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import wseemann.media.FFmpegMediaMetadataRetriever
class MyAdapter : RecyclerView.Adapter<MyAdapter.VideoHolder> {
private var mCtx: Context? = null
private var mItems = ArrayList<MyData>()
private var mFailItemCount = 0
private var mSuccessItemCount = 0
private var mTotalCostTime = 0L
private val mCache = LruCache<String, Bitmap?>(1000)
private var mIsDecoderCompleted = false
constructor(ctx: Context) : super() {
mCtx = ctx
}
fun dataChanged(items: ArrayList<MyData>) {
this.mItems = items
mSuccessItemCount = 0
mTotalCostTime = 0
mFailItemCount = 0
notifyDataSetChanged()
(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {
mIsDecoderCompleted = false
mItems.forEachIndexed { idx, data ->
var bmp: Bitmap? = mCache[data.toString()]
if (bmp == null) {
bmp = getFFMMRBmp(data)
if (bmp != null) {
mCache.put(data.toString(), bmp)
}
(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.Main) {
notifyItemChanged(idx)
}
}
}
mIsDecoderCompleted = true
withContext(Dispatchers.Main) {
notifyDataSetChanged()
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoHolder {
val v = LayoutInflater.from(mCtx).inflate(R.layout.image_layout, null, false)
return VideoHolder(v)
}
override fun onBindViewHolder(holder: VideoHolder, position: Int) {
loadVideoCover(mItems[position], holder.image)
}
override fun getItemCount(): Int {
return mItems.size
}
class VideoHolder : RecyclerView.ViewHolder {
var image: ImageView? = null
constructor(itemView: View) : super(itemView) {
image = itemView.findViewById<ImageView>(R.id.image)
image?.setImageResource(android.R.drawable.ic_menu_gallery)
}
}
private fun getFFMMRBmp(data: MyData): Bitmap? {
val ffMMR = FFmpegMediaMetadataRetriever()
var bmp: Bitmap? = null
val t = System.currentTimeMillis()
try {
ffMMR.setDataSource(data.path)
bmp = ffMMR.frameAtTime
mSuccessItemCount++
Log.d(
MainActivity.TAG,
"FFmpeg MMR:total success item count=$mSuccessItemCount"
)
} catch (e: Exception) {
Log.e(MainActivity.TAG, "FFmpeg MMR: total fail item count=${mFailItemCount++} , ${e.message}:$data")
} finally {
try {
ffMMR.release()
} catch (exc: Exception) {
Log.e(MainActivity.TAG, "$exc")
}
}
val costTime = System.currentTimeMillis() - t
mTotalCostTime = mTotalCostTime + costTime
Log.d(MainActivity.TAG, "FFmpeg MMR:total cost time= $mTotalCostTime ms")
return bmp
}
private fun loadVideoCover(data: MyData, image: ImageView?) {
val bmp: Bitmap? = mCache[data.toString()]
if (bmp != null) {
image?.setImageBitmap(bmp)
} else {
if (mIsDecoderCompleted) {
image?.setImageResource(android.R.drawable.stat_notify_error)
}
}
}
}
import android.net.Uri
open class MyData {
var uri: Uri? = null
var path: String? = null
var lastModified = 0L
var width = 0
var height = 0
var position = -1
var type = -1 //-1未知。1,普通图。2,视频。
constructor(uri: Uri?, path: String?, type: Int = -1) {
this.uri = uri
this.path = path
this.type = type
}
override fun toString(): String {
return "MyData(uri=$uri, path=$path, lastModified=$lastModified, width=$width, height=$height, position=$position, type=$type)"
}
override fun equals(other: Any?): Boolean {
return (this.toString()) == other.toString()
}
}