android 获取视频大小,Android 获取视频缩略图(获取视频每帧数据)的优化方案
速度对比
左边的图片是通过方式1
右边的图片是通过方式2
speed.gif
速度优化,效果拔群。
在缩小2倍的Bitmap输出情况下
使用MediaMetadataRetriever 抽帧的速度,每帧稳定在 300ms左右。
使用MediaCodec+ImageReader 第一次抽帧。大概是200ms ,后续每帧则是50ms左右。
注意:如果不缩小图片的话,建议还是使用MediaMetadataRetriever。
使用当前库的话,调用metadataRetriever.forceFallBack(true);
方案
1. 通过MediaMetaRetrivier来进行获取
代码较为简单,就是一个循环
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
metadataRetriever.setDataSource(fileName);
String duration = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
Log.d(TAG, "duration = " + duration);
int durationMs = Integer.parseInt(duration);
//每秒取一次
for (int i = 0; i < durationMs; i += 1000) {
long start = System.nanoTime();
Log.d(TAG, "getFrameAtTime time = " + i);
//这里传入的是ms
Bitmap frameAtIndex = metadataRetriever.getFrameAtTime(i * 1000);
Bitmap frame=Bitmap.createScaledBitmap(frameAtIndex,frameAtIndex.getWidth()/8,frameAtIndex.getHeight()/8,false);
frameAtIndex.recycle();
long end = System.nanoTime();
long cost = end - start;
Log.d(TAG, "cost time in millis = " + (cost * 1f / 1000000));
if (callBack != null) {
callBack.onComplete(frame);
}
}
metadataRetriever.release();
2. 通过MediaCodec和ImageReader进行获取
就是通过通过Surface,用MediaExtrator,将MediaCodec解码后的数据,传递给ImageReader。来进行显示。
MediaExtractor extractor = null;
MediaCodec codec = null;
try {
extractor = new MediaExtractor();
extractor.setDataSource(fileName);
int trackCount = extractor.getTrackCount();
MediaFormat videoFormat = null;
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = extractor.getTrackFormat(i);
if (trackFormat.getString(MediaFormat.KEY_MIME).contains("video")) {
videoFormat = trackFormat;
extractor.selectTrack(i);
break;
}
}
if (videoFormat == null) {
Log.d(TAG, "Can not get video format");
return;
}
int imageFormat = ImageFormat.YUV_420_888;
int colorFormat = COLOR_FormatYUV420Flexible;
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
videoFormat.setInteger(MediaFormat.KEY_WIDTH, videoFormat.getInteger(MediaFormat.KEY_WIDTH) / 4);
videoFormat.setInteger(MediaFormat.KEY_HEIGHT, videoFormat.getInteger(MediaFormat.KEY_HEIGHT) / 4);
long duration = videoFormat.getLong(MediaFormat.KEY_DURATION);
codec = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
ImageReader imageReader = ImageReader
.newInstance(
videoFormat.getInteger(MediaFormat.KEY_WIDTH),
videoFormat.getInteger(MediaFormat.KEY_HEIGHT),
imageFormat,
3);
final ImageReaderHandlerThread imageReaderHandlerThread = new ImageReaderHandlerThread();
imageReader.setOnImageAvailableListener(new MyOnImageAvailableListener(callBack), imageReaderHandlerThread.getHandler());
codec.configure(videoFormat, imageReader.getSurface(), null, 0);
codec.start();
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
long timeOut = 5 * 1000;//10ms
boolean inputDone = false;
boolean outputDone = false;
ByteBuffer[] inputBuffers = null;
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
inputBuffers = codec.getInputBuffers();
}
//开始进行解码。
int count = 1;
while (!outputDone) {
if (requestStop) {
return;
}
if (!inputDone) {
//feed data
int inputBufferIndex = codec.dequeueInputBuffer(timeOut);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
inputBuffer = codec.getInputBuffer(inputBufferIndex);
} else {
inputBuffer = inputBuffers[inputBufferIndex];
}
int sampleData = extractor.readSampleData(inputBuffer, 0);
if (sampleData > 0) {
long sampleTime = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferIndex, 0, sampleData, sampleTime, 0);
//继续
if (interval == 0) {
extractor.advance();
} else {
extractor.seekTo(count * interval * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
count++;
// extractor.advance();
}
} else {
//小于0,说明读完了
codec.queueInputBuffer(inputBufferIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
Log.d(TAG, "end of stream");
}
}
}
if (!outputDone) {
//get data
int status = codec.dequeueOutputBuffer(bufferInfo, timeOut);
if (status ==
MediaCodec.INFO_TRY_AGAIN_LATER) {
//继续
} else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
//开始进行解码
} else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
//同样啥都不做
} else {
//在这里判断,当前编码器的状态
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
Log.d(TAG, "output EOS");
outputDone = true;
}
boolean doRender = (bufferInfo.size != 0);
long presentationTimeUs = bufferInfo.presentationTimeUs;
if (lastPresentationTimeUs == 0) {
lastPresentationTimeUs = presentationTimeUs;
} else {
long diff = presentationTimeUs - lastPresentationTimeUs;
if (interval != 0) {
if (diff < interval * 1000) {
doRender = false;
} else {
lastPresentationTimeUs = presentationTimeUs;
}
Log.d(TAG,
"diff time in ms =" + diff / 1000);
}
}
//有数据了.因为会直接传递给Surface,所以说明都不做好了
Log.d(TAG, "surface decoder given buffer " + status +
" (size=" + bufferInfo.size + ")" + ",doRender = " + doRender + ", presentationTimeUs=" + presentationTimeUs);
//直接送显就可以了
codec.releaseOutputBuffer(status, doRender);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (codec != null) {
codec.stop();
codec.release();
}
if (extractor != null) {
extractor.release();
}
}
}
通过libyuv进行数据的转换
private static class MyOnImageAvailableListener implements ImageReader.OnImageAvailableListener {
private final BitmapCallBack callBack;
private MyOnImageAvailableListener(BitmapCallBack callBack) {
this.callBack = callBack;
}
@Override
public void onImageAvailable(ImageReader reader) {
Log.i(TAG, "in OnImageAvailable");
Image img = null;
try {
img = reader.acquireLatestImage();
if (img != null) {
//这里得到的YUV的数据。需要将YUV的数据变成Bitmap
Image.Plane[] planes = img.getPlanes();
if (planes[0].getBuffer() == null) {
return;
}
// Bitmap bitmap = getBitmap(img);
Bitmap bitmap = getBitmapScale(img, 8);
// Bitmap bitmap = getBitmapFromNv21(img);
if (callBack != null && bitmap != null) {
Log.d(TAG, "onComplete bitmap ");
callBack.onComplete(bitmap);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (img != null) {
img.close();
}
}
}
@NonNull
private Bitmap getBitmapScale(Image img, int scale) {
int width = img.getWidth() / scale;
int height = img.getHeight() / scale;
final byte[] bytesImage = getDataFromYUV420Scale(img, scale);
Bitmap bitmap = null;
bitmap = Bitmap.createBitmap(height, width, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(ByteBuffer.wrap(bytesImage));
return bitmap;
}
private byte[] getDataFromYUV420Scale(Image image, int scale) {
int width = image.getWidth();
int height = image.getHeight();
// Read image data
Image.Plane[] planes = image.getPlanes();
byte[] argb = new byte[width / scale * height / scale * 4];
//值得注意的是在Java层传入byte[]以RGBA顺序排列时,libyuv是用ABGR来表示这个排列
//libyuv表示的排列顺序和Bitmap的RGBA表示的顺序是反向的。
// 所以实际要调用libyuv::ABGRToI420才能得到正确的结果。
YuvUtils.yuvI420ToABGRWithScale(
argb,
planes[0].getBuffer(), planes[0].getRowStride(),
planes[1].getBuffer(), planes[1].getRowStride(),
planes[2].getBuffer(), planes[2].getRowStride(),
width, height,
scale
);
return argb;
}
}
libyuv
extern "C"
JNIEXPORT void JNICALL
Java_com_example_yuv_YuvUtils_yuvI420ToABGRWithScale(JNIEnv *env, jclass type, jbyteArray argb_,
jobject y_buffer, jint y_rowStride,
jobject u_buffer, jint u_rowStride,
jobject v_buffer, jint v_rowStride,
jint width, jint height,
jint scale) {
jbyte *argb = env->GetByteArrayElements(argb_, NULL);
uint8_t *srcYPtr = reinterpret_cast(env->GetDirectBufferAddress(y_buffer));
uint8_t *srcUPtr = reinterpret_cast(env->GetDirectBufferAddress(u_buffer));
uint8_t *srcVPtr = reinterpret_cast(env->GetDirectBufferAddress(v_buffer));
int scaleW = width / scale;
int scaleH = height / scale;
int scaleSize = scaleW * scaleH;
jbyte *temp_y_scale = new jbyte[scaleSize * 3 / 2];
jbyte *temp_u_scale = temp_y_scale + scaleSize;
jbyte *temp_v_scale = temp_y_scale + scaleSize + scaleSize / 4;
libyuv::I420Scale(
srcYPtr, y_rowStride,
srcUPtr, u_rowStride,
srcVPtr, v_rowStride,
width, height,
(uint8_t *) temp_y_scale, scaleW,
(uint8_t *) temp_u_scale, scaleW >> 1,
(uint8_t *) temp_v_scale, scaleW >> 1,
scaleW, scaleH,
libyuv::kFilterNone
);
width = scaleW;
height = scaleH;
jbyte *temp_y = new jbyte[width * height * 3 / 2];
jbyte *temp_u = temp_y + width * height;
jbyte *temp_v = temp_y + width * height + width * height / 4;
libyuv::I420Rotate(
(uint8_t *) temp_y_scale, scaleW,
(uint8_t *) temp_u_scale, scaleW >> 1,
(uint8_t *) temp_v_scale, scaleW >> 1,
//
(uint8_t *) temp_y, height,
(uint8_t *) temp_u, height >> 1,
(uint8_t *) temp_v, height >> 1,
width, height,
libyuv::kRotate90
);
libyuv::I420ToABGR(
(uint8_t *) temp_y, height,
(uint8_t *) temp_u, height >> 1,
(uint8_t *) temp_v, height >> 1,
(uint8_t *) argb, height * 4,
height, width
);
env->ReleaseByteArrayElements(argb_, argb, 0);
}
后续
将文件通过MediaCodec解码。 输出到ImageReader当中。来获取截图。
使用MediaMetadataRetriever的方式,因为无法配置输出的图片的大小。
但当我们只需要生成小图预览的时候, 如果我们实现做了缩放的处理。就能得到很快的速度。
不足
需要对原来MediaMetadataRetriever的原理探究
相关文章:

Msql的DML、DDL、DCL的区别
DML(data manipulation language):它们是SELECT、UPDATE、INSERT、DELETE,这4条命令是用来对数据库里的数据进行操作的语言 DDL(data definition language):主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的…
JSR 133 Java内存模型以及并发编程的最权威论文汇总
Java内存模型 先看官方文档: https://docs.oracle.com/javase/specs/ JSR 133:Java TM内存模型和线程规范修订版:https://www.jcp.org/en/jsr/detail?id133 JSR:Java规范请求所有JSR的列表:https://jcp.org/en/jsr/…

ajax实现自动刷新页面实例
html部分:<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>ajax实现自动刷新</title> </head> <body onLoad"Autofresh()"> <p>现在的时间是:…

android aliasactivity作用,android activity-alias 的作用
activity-alias是android里为了重复使用Activity而设计的。当在Activity的onCreate()方法里,执行getIntent().getComponent().getClassName();得到的可能不是这个Activity的名字,有可能是别名的名字,例如:在AndroidMenifest.xml有…

1024 程序员节 | 请对身边的程序猿好一点
程序员节起源程序员的工作我们都知道,编程嘛。但为什么程序员节要在1024呢?1024最早火起来是因为一个“不可描述”的论坛,那里的回帖机制是:新用户发过贴之后,过1024秒才能发一贴,如果没到1024秒就又发了一…

stackoverflow上一个最会举例子的专家
https://stackoverflow.com/ Premraj是stackoverflow上一个一个最会举例子的专家,我特意收集了他的一些有趣的举例: Java弱引用最精彩的解释 https://stackoverflow.com/questions/299659/whats-the-difference-between-softreference-and-weakrefere…

Java中的两个关键字——super、this
Java中的两个关键字——super、this 神话丿小王子的博客主页 一、super super 是java中方的一个关键字,用它可以引用父类中的成员: super可用于访问父类中定义的属性 super可用于调用父类中定义的成员方法 super可用于在子类构造器中调用父类的构造器 使…

android system window,Android控件的fitSystemWindows属性
官方描述:根据系统窗体里的元素比如状态栏来调整View的布局。如果被设为true,控件的padding将会被调整为顶部留出一个statusBar的空间。类似于伪代码paddingTop"statusBarHeight"。重点说明:当布局内容可以延伸到状态栏,…

Nestjs OpenAPI(Swagger)
官方文档 用来描述api 转载于:https://www.cnblogs.com/ajanuw/p/9846589.html

Jdk11,Jdk12的低延迟垃圾收集器ZGC
https://wiki.openjdk.java.net/display/zgc/Main Z垃圾收集器,也称为ZGC,是一种可扩展的低延迟垃圾收集器,旨在实现以下目标: 暂停时间不超过10毫秒暂停时间不会随堆或实时设置大小而增加处理堆范围从几百M到几T字节大小 一目了…

Android项目驱动式开发教程 第2版,《Android项目驱动式开发教程》第一章开发入门.ppt...
《Android项目驱动式开发教程》第一章开发入门1.4 项目框架分析 4 android:versionName"1.0" > 5 8 第9行代码android:icon用来声明整个APP的图标,图片一般都放在drawable文件夹下,使用资源引用的方式。 第10行代码android:label用来声明整…
getLocationInWindow getLocationOnScreen getLeft , getTop, getBottom,getRight
版权声明:本文为博主原创文章,未经博主允许不得转载。 最近做项目时,发现在activity的onCreate()和onResume()方法里调用View.getLocationInWindow() 时,View.getLocationInWindow()返回空值,觉得很奇怪,因…

使用reflector对.NET反编译
reflector的下载地址:https://www.cr173.com/soft/355285.html 反编译后的结果: 转载于:https://www.cnblogs.com/ZaraNet/p/9848355.html

协程和Java实现
多线程的性能问题: 1.同步锁。 2.线程阻塞状态和可运行状态之间的切换。 3.线程上下文的切换。 协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。 协程&a…

LoadRunner 技巧之 手动关联与预关联
上一节介绍了关联的原理与自动关联,除了自动关联还有另外两种关联方式:手动关联与 预关联。 手动关联 如果脚本很长,那么我们想找到一…

android app文档,android App项目需求描述文档.docx
本app是给外卖配送员用的,系统后台根据一定的逻辑生成或者建立运单,本App读到后台的运单讲外卖送到定外卖的手中本文档所需详细资料请到/s/1jGGgtLG下载与后台交互的地方不用实现,有数据显示的自己把交互函数写好 返回的测试数据写在交互函数…
如何利用微信小游戏的分包加载机制突破4M代码包体积限制
相信大家度过了一个不错的端午假期,在端午前夕,即6月15日晚上,微信小游戏宣布支持分包加载功能,白鹭引擎在端午节后第一天正式支持分包加载机制。在正式向开发者介绍如何使用前,我先为各位解读一下我对微信提供这个 AP…

一个会画图的工程师
发现小谢图画的很好,虽然有些也是他引用的,但是我觉得还是很好所以这里收集下。 【RocketMQ源码学习】2-Namesrv 3-Remoting模块 rocketmq-remoting 模块是 RocketMQ 中负责网络通信的模块,被其他所有需要网络通信的模块依赖。它是基于 Net…

2016百度实习编程题:括号序列
不知如何解决 1.感觉贪心或者动态规划,不知道如何解决 2.做过生成合法括号序列的题目,想到用DFS补成合法的括号,然而没有成功

html动画怎么隐藏,JQuery操作div隐藏和显示的4种动画
Jquery-Div动画显示body{font-family:"宋体";font-size:13px;color:#415973;}#ShowDiv{display:none;width:300px;height:100px;border:1px solid #ccc;border-right:2px solid #888;border-bottom:2px solid #888;background-color:#f9f9f9;text-align:center;paddi…

@芥末的糖----------《后端加密》
bcryptsession 生命周期 session 标识产生的时机和清除时机:(权限验证) 用户已经登录:这个唯一标识会在用户登录时产生,用户点击退出时或者关闭浏览器时清除。 …
Java中的ClassLoader和SPI机制
深入探讨 Java 类加载器 成富是著名的Java专家,在IBM技术网站发表很多Java好文,也有著作。 线程上下文类加载器 线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextCl…

html在线缓存视频,javascript – 如何为HTML视频添加缓冲
给你的视频提供ID:用于缓冲:var vid document.getElementById("myVideoOne");alert("Start: " vid.buffered.start(0) " End: " vid.buffered.end(0));var vid document.getElementById("myVideoTwo");aler…
ComponentName(String pkg, String cls)
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingfeng812/article/details/51279606 开发中因为改项目包名,用了全局替换,误操作把改构造函数的第二个参数只写了类名,不是完整的全路径…

mysql干货——数据库字符集和校对规则详解
2019独角兽企业重金招聘Python工程师标准>>> 一、什么是字符集 字符是多个文字和符号的总称,包括各个国家的文字、标点符号、图形符号、数字等。字符集多个字符的集合。 字符集合种类较多,每个字符集包含的字符的个数不同。对于字符集不支持的…

android监听器在哪里创建,[转载]android开发中创建按钮事件监听器的几种方法
第一种:匿名内部类作为事件监听器类Button button(Button)findViewById(R.id.button);button.setOnClickListener(newOnClickListener(){public void onClick(View v){System.out.println(“匿名内部类作为事件监听类”);}});大部分时候,事件处理器都没有…
Go语言源码分析CAS的实现和Java如出一辙
看了Go的源码CAS这块实现和java还是类似的。 关于Java的分析参考:Java使用字节码和汇编语言同步分析volatile,synchronized的底层实现 都是使用汇编指令:LOCKCMPXCHGL 原因很简单:单核肯定不能发挥Go的高并发性能,G…

python 的文件读写方法:read readline readlines wirte writelines
read()方法读入整个文件,把整个文件以字符串形式返回。readline() 方法每次只读一行,每运行一次只读一行。如果要读整个文件,需要结合循环判断文件结束。python判断文件末尾的标志是,读到空字符。比如:while str ! :r…

c# 模板方法模式
为什么80%的码农都做不了架构师?>>> 既然是模板肯定定义了相同的东西板式,提供了空白的地方自己添加进去就可以了 模板方法是把相同的部分抽象出来到抽象类中去定义,具体子类来实现具体的不同部分,这个思路也正式模板…
transient HashMap使用目的分析
看HashSet源码有这么一句: private transient HashMap<E,Object> map; 再看HashSet的Add方法: 实际上HashSet是复用HashMap了。 而我们去看看HashMap也会发现一样使用了transient 而不管是HashSet还是HashMapdou都要求是Serializable的ÿ…