Android快速视频解码抽帧FFmpegMediaMetadataRetriever,Kotlin(2)
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"><ImageViewandroid: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.launchclass MainActivity : AppCompatActivity() {companion object {const val TAG = "fly"const val SPAN_COUNT = 8const 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.VERTICALrv.layoutManager = layoutManagerval adapter = MyAdapter(this)rv.adapter = adapterrv.layoutManager = layoutManagerval ctx = thislifecycleScope.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>()//读取视频Videoval 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.FFmpegMediaMetadataRetrieverclass MyAdapter : RecyclerView.Adapter<MyAdapter.VideoHolder> {private var mCtx: Context? = nullprivate var mItems = ArrayList<MyData>()private var mFailItemCount = 0private var mSuccessItemCount = 0private var mTotalCostTime = 0Lprivate val mCache = LruCache<String, Bitmap?>(1000)private var mIsDecoderCompleted = falseconstructor(ctx: Context) : super() {mCtx = ctx}fun dataChanged(items: ArrayList<MyData>) {this.mItems = itemsmSuccessItemCount = 0mTotalCostTime = 0mFailItemCount = 0notifyDataSetChanged()(mCtx as AppCompatActivity).lifecycleScope.launch(Dispatchers.IO) {mIsDecoderCompleted = falsemItems.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 = truewithContext(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? = nullconstructor(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? = nullval t = System.currentTimeMillis()try {ffMMR.setDataSource(data.path)bmp = ffMMR.frameAtTimemSuccessItemCount++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() - tmTotalCostTime = mTotalCostTime + costTimeLog.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.Uriopen class MyData {var uri: Uri? = nullvar path: String? = nullvar lastModified = 0Lvar width = 0var height = 0var position = -1var type = -1 //-1未知。1,普通图。2,视频。constructor(uri: Uri?, path: String?, type: Int = -1) {this.uri = urithis.path = paththis.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()}
}
Android MediaMetadataRetriever取视频封面,Kotlin(1)-CSDN博客文章浏览阅读713次,点赞17次,收藏11次。该Android项目实现了一个视频缩略图展示功能,主要包含以下内容:1)声明了读写存储权限;2)使用RecyclerView以9列网格布局展示视频;3)通过MediaMetadataRetriever获取视频首帧作为缩略图;4)采用协程处理耗时操作,避免阻塞主线程。项目包含MainActivity、MyAdapter和MyData三个核心类,分别负责UI初始化、数据适配和数据封装。遇到视频损坏或0字节文件时,会显示错误图标并记录日志。整体实现了高效读取设备视频并生成缩略图展示的功能。https://blog.csdn.net/zhangphil/article/details/150023739