当前位置: 首页 > 编程日记 > 正文

android 获取视频大小,Android 获取视频缩略图(获取视频每帧数据)的优化方案

速度对比

左边的图片是通过方式1

右边的图片是通过方式2

dfddb85302bd

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)&#xff1a;它们是SELECT、UPDATE、INSERT、DELETE&#xff0c;这4条命令是用来对数据库里的数据进行操作的语言 DDL(data definition language)&#xff1a;主要的命令有CREATE、ALTER、DROP等&#xff0c;DDL主要是用在定义或改变表(TABLE)的…

JSR 133 Java内存模型以及并发编程的最权威论文汇总

Java内存模型 先看官方文档&#xff1a; https://docs.oracle.com/javase/specs/ JSR 133&#xff1a;Java TM内存模型和线程规范修订版&#xff1a;https://www.jcp.org/en/jsr/detail?id133 JSR&#xff1a;Java规范请求所有JSR的列表&#xff1a;https://jcp.org/en/jsr/…

ajax实现自动刷新页面实例

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

android aliasactivity作用,android activity-alias 的作用

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

1024 程序员节 | 请对身边的程序猿好一点

程序员节起源程序员的工作我们都知道&#xff0c;编程嘛。但为什么程序员节要在1024呢&#xff1f;1024最早火起来是因为一个“不可描述”的论坛&#xff0c;那里的回帖机制是&#xff1a;新用户发过贴之后&#xff0c;过1024秒才能发一贴&#xff0c;如果没到1024秒就又发了一…

stackoverflow上一个最会举例子的专家

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

Java中的两个关键字——super、this

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

android system window,Android控件的fitSystemWindows属性

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

Nestjs OpenAPI(Swagger)

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

Jdk11,Jdk12的低延迟垃圾收集器ZGC

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

Android项目驱动式开发教程 第2版,《Android项目驱动式开发教程》第一章开发入门.ppt...

《Android项目驱动式开发教程》第一章开发入门1.4 项目框架分析 4 android:versionName"1.0" > 5 8 第9行代码android:icon用来声明整个APP的图标&#xff0c;图片一般都放在drawable文件夹下&#xff0c;使用资源引用的方式。 第10行代码android:label用来声明整…

getLocationInWindow getLocationOnScreen getLeft , getTop, getBottom,getRight

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 最近做项目时&#xff0c;发现在activity的onCreate()和onResume()方法里调用View.getLocationInWindow() 时&#xff0c;View.getLocationInWindow()返回空值&#xff0c;觉得很奇怪&#xff0c;因…

使用reflector对.NET反编译

reflector的下载地址&#xff1a;https://www.cr173.com/soft/355285.html 反编译后的结果&#xff1a; 转载于:https://www.cnblogs.com/ZaraNet/p/9848355.html

协程和Java实现

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

LoadRunner 技巧之 手动关联与预关联

上一节介绍了关联的原理与自动关联&#xff0c;除了自动关联还有另外两种关联方式&#xff1a;手动关联与 预关联。 手动关联 如果脚本很长&#xff0c;那么我们想找到一…

android app文档,android App项目需求描述文档.docx

本app是给外卖配送员用的&#xff0c;系统后台根据一定的逻辑生成或者建立运单&#xff0c;本App读到后台的运单讲外卖送到定外卖的手中本文档所需详细资料请到/s/1jGGgtLG下载与后台交互的地方不用实现&#xff0c;有数据显示的自己把交互函数写好 返回的测试数据写在交互函数…

如何利用微信小游戏的分包加载机制突破4M代码包体积限制

相信大家度过了一个不错的端午假期&#xff0c;在端午前夕&#xff0c;即6月15日晚上&#xff0c;微信小游戏宣布支持分包加载功能&#xff0c;白鹭引擎在端午节后第一天正式支持分包加载机制。在正式向开发者介绍如何使用前&#xff0c;我先为各位解读一下我对微信提供这个 AP…

一个会画图的工程师

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

2016百度实习编程题:括号序列

不知如何解决 1.感觉贪心或者动态规划&#xff0c;不知道如何解决 2.做过生成合法括号序列的题目&#xff0c;想到用DFS补成合法的括号&#xff0c;然而没有成功

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 标识产生的时机和清除时机:&#xff08;权限验证&#xff09; 用户已经登录&#xff1a;这个唯一标识会在用户登录时产生&#xff0c;用户点击退出时或者关闭浏览器时清除。 …

Java中的ClassLoader和SPI机制

深入探讨 Java 类加载器 成富是著名的Java专家&#xff0c;在IBM技术网站发表很多Java好文&#xff0c;也有著作。 线程上下文类加载器 线程上下文类加载器&#xff08;context class loader&#xff09;是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextCl…

html在线缓存视频,javascript – 如何为HTML视频添加缓冲

给你的视频提供ID&#xff1a;用于缓冲&#xff1a;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)

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/qingfeng812/article/details/51279606 开发中因为改项目包名&#xff0c;用了全局替换&#xff0c;误操作把改构造函数的第二个参数只写了类名&#xff0c;不是完整的全路径…

mysql干货——数据库字符集和校对规则详解

2019独角兽企业重金招聘Python工程师标准>>> 一、什么是字符集 字符是多个文字和符号的总称&#xff0c;包括各个国家的文字、标点符号、图形符号、数字等。字符集多个字符的集合。 字符集合种类较多&#xff0c;每个字符集包含的字符的个数不同。对于字符集不支持的…

android监听器在哪里创建,[转载]android开发中创建按钮事件监听器的几种方法

第一种&#xff1a;匿名内部类作为事件监听器类Button button(Button)findViewById(R.id.button);button.setOnClickListener(newOnClickListener(){public void onClick(View v){System.out.println(“匿名内部类作为事件监听类”);}});大部分时候&#xff0c;事件处理器都没有…

Go语言源码分析CAS的实现和Java如出一辙

看了Go的源码CAS这块实现和java还是类似的。 关于Java的分析参考&#xff1a;Java使用字节码和汇编语言同步分析volatile&#xff0c;synchronized的底层实现 都是使用汇编指令&#xff1a;LOCKCMPXCHGL 原因很简单&#xff1a;单核肯定不能发挥Go的高并发性能&#xff0c;G…

python 的文件读写方法:read readline readlines wirte writelines

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

c# 模板方法模式

为什么80%的码农都做不了架构师&#xff1f;>>> 既然是模板肯定定义了相同的东西板式&#xff0c;提供了空白的地方自己添加进去就可以了 模板方法是把相同的部分抽象出来到抽象类中去定义&#xff0c;具体子类来实现具体的不同部分&#xff0c;这个思路也正式模板…

transient HashMap使用目的分析

看HashSet源码有这么一句&#xff1a; private transient HashMap<E,Object> map; 再看HashSet的Add方法&#xff1a; 实际上HashSet是复用HashMap了。 而我们去看看HashMap也会发现一样使用了transient 而不管是HashSet还是HashMapdou都要求是Serializable的&#xff…