API 27中getScaledFrameAtTime返回Bitmap尺寸异常的原因及解决
问题分析与解决办法
我之前在处理视频帧提取的时候也碰到过类似的问题,咱们来一步步拆解原因和解决思路:
可能的异常原因
- 宽高比自动适配机制:
getScaledFrameAtTime方法会严格保持原视频的宽高比进行缩放。如果你的原视频是9:16的竖屏比例,当你指定目标高度为224时,宽度会自动计算为224 * (9/16) = 126,这完全符合方法的设计逻辑,并不是bug。 - 线程安全问题:
MediaMetadataRetriever本身不是线程安全的,如果你多个线程共用同一个实例调用setDataSource或getScaledFrameAtTime,会导致内部状态混乱,可能返回错误尺寸的Bitmap,甚至抛出异常。 - API 27版本兼容性:在Android 8.1(API 27)中,该方法对某些特殊编码格式的视频(比如部分H.265编码或非常规分辨率的视频)处理可能存在兼容性问题,导致缩放逻辑异常。
预防与处理方案
针对宽高比问题的处理
如果你需要严格得到224x224的Bitmap,可以按以下步骤操作:
- 先获取原视频的宽高信息:
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(uri); int videoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)); int videoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)); - 计算符合原比例的缩放尺寸,再裁剪或填充到224x224:
- 方式一:裁剪出中心区域的224x224
Bitmap scaledBitmap = retriever.getScaledFrameAtTime(timeStamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST, 224, 224); if (scaledBitmap != null) { int xOffset = (scaledBitmap.getWidth() - 224) / 2; int yOffset = (scaledBitmap.getHeight() - 224) / 2; Bitmap targetBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, yOffset, 224, 224); scaledBitmap.recycle(); // 及时回收避免内存泄漏 } - 方式二:将缩放后的Bitmap填充到224x224的画布(补黑边或透明边)
Bitmap scaledBitmap = retriever.getScaledFrameAtTime(timeStamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST, 224, 224); if (scaledBitmap != null) { Bitmap targetBitmap = Bitmap.createBitmap(224, 224, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(targetBitmap); canvas.drawColor(Color.BLACK); // 填充背景色 float scale = Math.max((float)224/scaledBitmap.getWidth(), (float)224/scaledBitmap.getHeight()); float dx = (224 - scaledBitmap.getWidth()*scale)/2; float dy = (224 - scaledBitmap.getHeight()*scale)/2; canvas.drawBitmap(scaledBitmap, dx, dy, new Paint()); scaledBitmap.recycle(); }
- 方式一:裁剪出中心区域的224x224
解决线程安全问题
每个线程/Runnable都创建独立的MediaMetadataRetriever实例,用完后记得调用release()释放资源:
new Thread(() -> { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { retriever.setDataSource(uri); Bitmap bitmap = retriever.getScaledFrameAtTime(timeStamp * 1000, MediaMetadataRetriever.OPTION_CLOSEST, 224, 224); // 处理Bitmap } catch (Exception e) { e.printStackTrace(); } finally { retriever.release(); } }).start();
兼容性问题处理
- 尝试升级目标API版本到28及以上,后续版本对
MediaMetadataRetriever的实现做了优化; - 如果必须兼容API 27,可以尝试使用
getFrameAtTime获取原始尺寸的Bitmap,再手动缩放裁剪到224x224,避免依赖系统的缩放逻辑; - 检查视频编码格式,对于非常规编码的视频,可以先转码为H.264等兼容性更好的格式。
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

