android获取surfaceview里面的每一帧

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android获取surfaceview里面的每一帧相关的知识,希望对你有一定的参考价值。

屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。
上述过程中的计算,对于View来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()、onLayout()、onDraw()来定义帧内容,但不能定义帧刷新频率。
SurfaceView可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView的模版代码:
public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
//用于计算帧数据的线程
private HandlerThread handlerThread;
private Handler handler;
//帧刷新频率
private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
//用于绘制帧的画布
private Canvas canvas;
private boolean isAlive;
public BaseSurfaceView(Context context)
super(context);
init();

protected void init()
getHolder().addCallback(this);
//设置透明背景,否则SurfaceView背景是黑的
setBackgroundTransparent();

private void setBackgroundTransparent()
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);

@Override
public void surfaceCreated(SurfaceHolder holder)
isAlive = true;
startDrawThread();

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)

@Override
public void surfaceDestroyed(SurfaceHolder holder)
stopDrawThread();
isAlive = false;

//停止帧绘制线程
private void stopDrawThread()
handlerThread.quit();
handler = null;

//启动帧绘制线程
private void startDrawThread()
handlerThread = new HandlerThread(“SurfaceViewThread”);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new DrawRunnable());

private class DrawRunnable implements Runnable
@Override
public void run()
if (!isAlive)
return;

try
//1.获取画布
canvas = getHolder().lockCanvas();
//2.绘制一帧
onFrameDraw(canvas);
catch (Exception e)
e.printStackTrace();
finally
//3.将帧数据提交
getHolder().unlockCanvasAndPost(canvas);
//4.一帧绘制结束
onFrameDrawFinish();

//不停的将自己推送到绘制线程的消息队列以实现帧刷新
handler.postDelayed(this, frameDuration);


protected abstract void onFrameDrawFinish();
protected abstract void onFrameDraw(Canvas canvas);

用HandlerThread作为独立帧绘制线程,好处是可以通过与其绑定的Handler方便地实现“每隔一段时间刷新”,而且在Surface被销毁的时候可以方便的调用HandlerThread.quit()来结束线程执行的逻辑。
DrawRunnable.run()运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView的子类FrameSurfaceView:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView
public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE;
private List bitmaps = new ArrayList<>();
//帧图片
private Bitmap frameBitmap;
//帧索引
private int bitmapIndex = INVALID_BITMAP_INDEX;
private Paint paint = new Paint();
private BitmapFactory.Options options = new BitmapFactory.Options();
//帧图片原始大小
private Rect srcRect;
//帧图片目标大小
private Rect dstRect = new Rect();
private int defaultWidth;
private int defaultHeight;
public void setDuration(int duration)
int frameDuration = duration / bitmaps.size();
setFrameDuration(frameDuration);

public void setBitmaps(List bitmaps)
if (bitmaps == null || bitmaps.size() == 0)
return;

this.bitmaps = bitmaps;
//默认情况下,计算第一帧图片的原始大小
getBitmapDimension(bitmaps.get(0));

private void getBitmapDimension(Integer integer)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(this.getResources(), integer, options);
defaultWidth = options.outWidth;
defaultHeight = options.outHeight;
srcRect = new Rec
参考技术A 屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。

上述过程中的计算,对于View来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()、onLayout()、onDraw()来定义帧内容,但不能定义帧刷新频率。

SurfaceView可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView的模版代码:

public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
//用于计算帧数据的线程
private HandlerThread handlerThread;
private Handler handler;
//帧刷新频率
private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
//用于绘制帧的画布
private Canvas canvas;
private boolean isAlive;

public BaseSurfaceView(Context context)
super(context);
init();


protected void init()
getHolder().addCallback(this);
//设置透明背景,否则SurfaceView背景是黑的
setBackgroundTransparent();


private void setBackgroundTransparent()
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);


@Override
public void surfaceCreated(SurfaceHolder holder)
isAlive = true;
startDrawThread();


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)


@Override
public void surfaceDestroyed(SurfaceHolder holder)
stopDrawThread();
isAlive = false;


//停止帧绘制线程
private void stopDrawThread()
handlerThread.quit();
handler = null;


//启动帧绘制线程
private void startDrawThread()
handlerThread = new HandlerThread(“SurfaceViewThread”);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new DrawRunnable());


private class DrawRunnable implements Runnable

@Override
public void run()
if (!isAlive)
return;

try
//1.获取画布
canvas = getHolder().lockCanvas();
//2.绘制一帧
onFrameDraw(canvas);
catch (Exception e)
e.printStackTrace();
finally
//3.将帧数据提交
getHolder().unlockCanvasAndPost(canvas);
//4.一帧绘制结束
onFrameDrawFinish();

//不停的将自己推送到绘制线程的消息队列以实现帧刷新
handler.postDelayed(this, frameDuration);



protected abstract void onFrameDrawFinish();

protected abstract void onFrameDraw(Canvas canvas);


用HandlerThread作为独立帧绘制线程,好处是可以通过与其绑定的Handler方便地实现“每隔一段时间刷新”,而且在Surface被销毁的时候可以方便的调用HandlerThread.quit()来结束线程执行的逻辑。
DrawRunnable.run()运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView的子类FrameSurfaceView:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView
public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE;
private List bitmaps = new ArrayList<>();
//帧图片
private Bitmap frameBitmap;
//帧索引
private int bitmapIndex = INVALID_BITMAP_INDEX;
private Paint paint = new Paint();
private BitmapFactory.Options options = new BitmapFactory.Options();
//帧图片原始大小
private Rect srcRect;
//帧图片目标大小
private Rect dstRect = new Rect();
private int defaultWidth;
private int defaultHeight;

public void setDuration(int duration)
int frameDuration = duration / bitmaps.size();
setFrameDuration(frameDuration);


public void setBitmaps(List bitmaps)
if (bitmaps == null || bitmaps.size() == 0)
return;

this.bitmaps = bitmaps;
//默认情况下,计算第一帧图片的原始大小
getBitmapDimension(bitmaps.get(0));


private void getBitmapDimension(Integer integer)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(this.getResources(), integer, options);
defaultWidth = options.outWidth;
defaultHeight = options.outHeight;
srcRect = new Rec
参考技术B 屏幕的显示机制和帧动画类似,也是一帧一帧的连环画,只不过刷新频率很高,感觉像连续的。为了显示一帧,需要经历计算和渲染两个过程,CPU 先计算出这一帧的图像数据并写入内存,然后调用 OpenGL 命令将内存中数据渲染成图像存放在 GPU Buffer 中,显示设备每隔一定时间从 Buffer 中获取图像并显示。
上述过程中的计算,对于View来说,就好比在主线程遍历 View树 以决定视图画多大(measure),画在哪(layout),画些啥(draw),计算结果存放在内存中,SurfaceFlinger 会调用 OpenGL 命令将内存中的数据渲染成图像存放在 GPU Buffer 中。每隔16.6ms,显示器从 Buffer 中取出帧并显示。所以自定义 View 可以通过重载onMeasure()、onLayout()、onDraw()来定义帧内容,但不能定义帧刷新频率。
SurfaceView可以突破这个限制。而且它可以将计算帧数据放到独立的线程中进行。下面是自定义SurfaceView的模版代码:
public abstract class BaseSurfaceView extends SurfaceView implements SurfaceHolder.Callback
public static final int DEFAULT_FRAME_DURATION_MILLISECOND = 50;
//用于计算帧数据的线程
private HandlerThread handlerThread;
private Handler handler;
//帧刷新频率
private int frameDuration = DEFAULT_FRAME_DURATION_MILLISECOND;
//用于绘制帧的画布
private Canvas canvas;
private boolean isAlive;
public BaseSurfaceView(Context context)
super(context);
init();

protected void init()
getHolder().addCallback(this);
//设置透明背景,否则SurfaceView背景是黑的
setBackgroundTransparent();

private void setBackgroundTransparent()
getHolder().setFormat(PixelFormat.TRANSLUCENT);
setZOrderOnTop(true);

@Override
public void surfaceCreated(SurfaceHolder holder)
isAlive = true;
startDrawThread();

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)

@Override
public void surfaceDestroyed(SurfaceHolder holder)
stopDrawThread();
isAlive = false;

//停止帧绘制线程
private void stopDrawThread()
handlerThread.quit();
handler = null;

//启动帧绘制线程
private void startDrawThread()
handlerThread = new HandlerThread(“SurfaceViewThread”);
handlerThread.start();
handler = new Handler(handlerThread.getLooper());
handler.post(new DrawRunnable());

private class DrawRunnable implements Runnable
@Override
public void run()
if (!isAlive)
return;

try
//1.获取画布
canvas = getHolder().lockCanvas();
//2.绘制一帧
onFrameDraw(canvas);
catch (Exception e)
e.printStackTrace();
finally
//3.将帧数据提交
getHolder().unlockCanvasAndPost(canvas);
//4.一帧绘制结束
onFrameDrawFinish();

//不停的将自己推送到绘制线程的消息队列以实现帧刷新
handler.postDelayed(this, frameDuration);


protected abstract void onFrameDrawFinish();
protected abstract void onFrameDraw(Canvas canvas);

用HandlerThread作为独立帧绘制线程,好处是可以通过与其绑定的Handler方便地实现“每隔一段时间刷新”,而且在Surface被销毁的时候可以方便的调用HandlerThread.quit()来结束线程执行的逻辑。
DrawRunnable.run()运用模版方法模式定义了绘制算法框架,其中帧绘制逻辑的具体实现被定义成两个抽象方法,推迟到子类中实现,因为绘制的东西是多样的,对于本文来说,绘制的就是一张张图片,所以新建BaseSurfaceView的子类FrameSurfaceView:
逐帧解析 & 及时回收
public class FrameSurfaceView extends BaseSurfaceView
public static final int INVALID_BITMAP_INDEX = Integer.MAX_VALUE;
private List bitmaps = new ArrayList<>();
//帧图片
private Bitmap frameBitmap;
//帧索引
private int bitmapIndex = INVALID_BITMAP_INDEX;
private Paint paint = new Paint();
private BitmapFactory.Options options = new BitmapFactory.Options();
//帧图片原始大小
private Rect srcRect;
//帧图片目标大小
private Rect dstRect = new Rect();
private int defaultWidth;
private int defaultHeight;
public void setDuration(int duration)
int frameDuration = duration / bitmaps.size();
setFrameDuration(frameDuration);

public void setBitmaps(List bitmaps)
if (bitmaps == null || bitmaps.size() == 0)
return;

this.bitmaps = bitmaps;
//默认情况下,计算第一帧图片的原始大小
getBitmapDimension(bitmaps.get(0));

private void getBitmapDimension(Integer integer)
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(this.getResources(), integer, options);
defaultWidth = options.outWidth;
defaultHeight = options.outHeight;
srcRect = new Rec

Unity3D导出SpriteMode为Multiple时,里面的每一张精灵图片。

有时候,我们会需要把图集中的每一个精灵导出来。

技术分享

导出代码:

  [MenuItem("Tools/ExportSprite")]
    public static void ExportSprite()
    {
        // 拿到选中的资源
        Object[] selects = Selection.objects;

        // 
        string savePath = Application.dataPath + "/outSprite/";
        Directory.CreateDirectory(savePath);
        foreach (Object item in selects)
        {
            Sprite sprite = item as Sprite;
            if (sprite == null)
                continue;

            // 获取精灵的贴图
            Texture2D t = sprite.texture;

            // 创建一个新的贴图
            Texture2D newTex = new Texture2D((int)sprite.rect.width, (int)sprite.rect.height,  TextureFormat.ARGB32, false);
            // 设置像素点为 选择贴图的像素点
            newTex.SetPixels(t.GetPixels((int)sprite.rect.xMin, (int)sprite.rect.yMin, (int)sprite.rect.width, (int)sprite.rect.height));
            
            newTex.Apply();

            // 把创建的贴图对象,转换为bytes
            byte[] buffer = newTex.EncodeToPNG();
            // 写出
            File.WriteAllBytes(savePath + sprite.name + ".png", buffer);
        }
    }

使用:

选中你要导出的精灵,点击头部菜单 Tools > ExportSprite 然后导出的精灵会放到 Project 下的 outSprite 目录中。

以上是关于android获取surfaceview里面的每一帧的主要内容,如果未能解决你的问题,请参考以下文章

Android使用SurfaceView搭建OpenGL环境

Android使用SurfaceView搭建OpenGL环境

Android使用SurfaceView搭建OpenGL环境

Android SurfaceView内容居中显示

android surfaceview 怎么用

[android] surfaceview的生命周期