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的原理探究
android 获取视频大小,Android 获取视频缩略图(获取视频每帧数据)的优化方案相关推荐
- Android解码视频每一帧,Android 获取视频缩略图(获取视频每帧数据)的优化方案
速度对比 左边的图片是通过方式1 右边的图片是通过方式2 speed.gif 速度优化,效果拔群. 在缩小2倍的Bitmap输出情况下 使用MediaMetadataRetriever 抽帧的速度,每 ...
- android 设置视频大小,android – 调整视频大小
我正在尝试动态设置 Android VideoView的大小.我看过StackOverflow以及互联网;我找到的最佳解决方案是从 here开始.我已经将我的实现放在下面: public class ...
- 获取屏幕大小 android,Android获取屏幕大小
屏幕像素--包含虚拟键 华为P9,参数规格: 分辨率 FHD 1920×1080 获取屏幕像素 DisplayMetrics dm = new DisplayMetrics(); getWindowM ...
- Android通知栏字体大小,Android通知栏介绍与适配总结(上篇)
此文已由作者黎星授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 由于历史原因,Android在发布之初对通知栏Notification的设计相当简单,而如今面对各式各样的通知 ...
- android程序字体大小,Android如何动态调整应用字体大小详解
前言 为什么要动态设置字体大小?由于项目面对的是中老年客户项目中自带的字体无法满足客户需求. Android应用字体大小默认随系统设置的字体大小而变化,但您可能不希望您的应用字体大小随系统设置变化,想 ...
- android程序字体大小,Android 动态调整应用字体大小
Android应用字体大小默认随系统设置的字体大小而变化,但您可能不希望您的应用字体大小随系统设置变化,想要自己控制,例如微信.本文简单介绍一下如何实现应用字体大小动态调整而不是依赖系统设置 字体大小 ...
- android bitmap内存大小,Android——Bitmap占用的内存大小
一.作用 知其然 二.概念 1. 几个小概念 像素:组成画面的基本单位,像素没有物理尺寸.可以任意缩放去适配显示屏的像素点大小.不同设备上一个单位像素色块的大小是不一样的 (2)dp 设备独立像素,用 ...
- android list字体大小,android自定义ListPreference字体大小
这篇博客算是自己的一个记录吧,组长给了一个任务,需要实现一个紧急广播的一系列设置功能,但是客户那边设置界面的字体大小有特殊的要求,看了一下,对于ListPreference这样的控件,android系 ...
- android 评价 星星大小,Android RatingBar 评价栏 星星 大小 样式 设置
Android系统自带RatingBar,无法直接指定宽高,不然会出现无法全部显示的情况,使用系统自带的两个主题 ?android:attr/ratingBarStyleIndicator和 ?and ...
最新文章
- 一个由印度人编写的VC串口类
- 软工作业——四则运算生成器(scala 实现)
- 【HTML】处理<br>换行符追加到前端换行无效的问题 --- html中渲染的字符串中包含HTML标签无效的处理方法,字符串中包含HTML标签被转义的问题 解决
- 如何在Mac OSX Yosemite中将Ruby版本2.0.0更新到最新版本?
- qt 回调函数设置界面_回调函数实现类似QT中信号机制(最简单)
- 微型计算机技术6,微型计算机技术课后习题6-8章答案.ppt
- 第三届空间信息智能服务研讨会
- 数据可视化|用热力地图进行数据分析
- 通信原理及系统系列4—— AWGN信道(信噪比SNR、Es/N0和Eb/N0概念的辨析、转换及使用)
- 计算机软件系统两大类,详解计算机软件系统包括哪两大类
- mscorsvw.exe占内存解决方案
- HostGator 評價 – 優異的運行時間與支持一鍵安裝 WordPress,內含 4 折 60% 折扣優惠購買連結! - TechMoon 科技月球...
- linux交叉编译 windows,从Windows到Linux的C ++交叉编译器
- 1990-2020年江苏省全省人口数、户数(常住)
- 开环放大倍数和闭环放大倍数的区别
- css inherit
- 05,数据采集:怎样做好全量全要素连接和实时反馈
- 计算机开机右下角无显示桌面,电脑开机后不显示桌面的原因
- mysql 数据割接_数据割接笔记
- Latex标记通讯作者信封出现type3字体解决办法