`
phenom
  • 浏览: 405998 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

Android camera 预览帧数和视频通话图片缓存

 
阅读更多
setPreviewFrameRate是在api level1就开始使用了,然后不是简单地设置这个方法就可以让摄像头每秒捕获多少帧数的。
比如我设置2,它一秒不会只捕获2帧数据的,从日志记录来看,相当糟糕,不会是预期的2帧,于是我查找文档,发现这个方法已经废除了。

在api level9时加入了一个方法setPreviewFpsRange (int min, int max)
预览帧数从min到max,这个值再*1000.
这个方法已经在高版本的sdk中取代了旧的setPreviewFrameRate。

如何知道摄像头的预览范围呢?我原以为从1到n,其实不然。
getSupportedPreviewFpsRange()这个方法就可以显示出你的手机摄像头支持的范围。
List<int[]> range=parameters.getSupportedPreviewFpsRange();
        Log.d(TAG, "range:"+range.size());
        for(int j=0;j<range.size();j++) {
        	int[] r=range.get(j);
        	for(int k=0;k<r.length;k++) {
        		Log.d(TAG, TAG+r[k]);
        	}
        }

如i9000会是7-30而不是1-30.。。
所以在Camera.PreviewCallback回调中,onPreviewFrame会得到的帧数就不会小于7了。

我还一直以为预览帧数是2,看到的画面还是很流畅。。。

文档里还有这么一段话:
Gets the supported preview fps (frame-per-second) ranges. Each range contains a minimum fps and maximum fps. If minimum fps equals to maximum fps, the camera outputs frames in fixed frame rate. If not, the camera outputs frames in auto frame rate. The actual frame rate fluctuates between the minimum and the maximum. The values are multiplied by 1000 and represented in integers. For example, if frame rate is 26.623 frames per second, the value is 26623.

如果最大值与最小值是一样的,就是以这个值输出预览,如果不同,则会以这个区间自动输出。
看上去,预览帧数还是不可控制的啊

这样只能自己控制帧数了。比如视频通话的就可以这样处理:

    private BlockingQueue<Object> mFrameList = new ArrayBlockingQueue<Object>(18);存放的帧最多18,多少根据自己的机器状况和网络状态调整,至少我觉得如果缓存太多,内存紧张,而且如果网络不好,一样发不出去,缓存太少,网络状况好的时候就会出现空帧,图像一样不流畅。
当然最好是还是c的编码速度够快,其它都是扯淡。

private void putVideoFrame(byte[] data,int width,int height){
        if(!showVideo){
            Log.d(TAG, "putVideoFrame.已经没有显示视频了。");
            return;
        }
        Log.d(TAG, "putVideoFrame.");
        ///synchronized(mFrameList){
            Object[] obj=new Object[]{data,width,height};
            mFrameList.offer(obj);
        //}
//这个过程本身就是同步的,把预览的帧放入缓存中等待发送。
    }
private PreviewCallback previewCallback=new PreviewCallback() {

        @Override
        public void onPreviewFrame(byte[] _data, Camera _camera) {
            Camera.Parameters parameters=_camera.getParameters();
            final Camera.Size size=parameters.getPreviewSize();
         
                final byte[] copyData = new byte[_data.length];
                System.arraycopy(_data, 0, copyData, 0, _data.length);

                putVideoFrame(copyData, size.width, size.height);
        }
    };
然后建一个线程:
Runnable frameRunnable = new Runnable() {

        @Override
        public void run() {
            while (showVideo&&mStarted) {
                sendVideoFrame();//在这里把帧发出去
                
                if(mFrameList.size()>=mFps){
                	fcount++;
                    Log.d(TAG, "缓存帧又满了。");

                    Object[] obj=(Object[]) mFrameList.poll();
                    mFrameList.clear();
                    mFrameList.offer(obj);
                }
            }
            
            Log.d(TAG, "结束发送线程.");
            //mFrameList.clear();
        }
    };
//满的时候需要将原来的清空,个人觉得如果缓存满了不清空,新预览的帧就会排在后面,如果一定要按顺序发送的话,还是不能达到流畅的效果。

最后是发送的:
private void sendVideoFrame() {
        //Log.d(TAG, "sendVideoFrame.");
        Object[] obj=null;
            obj=(Object[]) mFrameList.poll();
        Log.d(TAG, "obj:"+obj);
        if (null!=obj) {
            long start=System.currentTimeMillis();
            byte[] data=(byte[]) obj[0];
            Integer width=(Integer) obj[1];
            Integer height=(Integer) obj[2];
            
            if (发送条件) {
                int res=engine.SendVideoFrame(data, width, height);
                long end=System.currentTimeMillis();
                start=end-start;
                //限制发送的帧数。
                end=delta-start;
                Log.d(TAG, "发送一次图像需要时间:"+(start)+" width:"+width+" height:"+height+
                    " count:"+count+++" res:"+res+" fcount:"+fcount+" end:"+end);
                if (end>0) {//为什么在这里睡眠呢,假设现在的网络好,机器强劲,其实也没有必要一直发送视频帧的,这里就可 以限制发送。预计发送一次为200毫秒,一秒为5帧。那发送的时间小于200的就要等待了。这样就限制了,如果多于200毫秒的,就不要睡眠了。个人感觉,如果帧数到达12左右,基本感觉是较流畅了,再往上,不会有太大的区别(前提:目前手机也达到不20+,而且网络。。。)
                    try {
                        Thread.sleep(end);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                Log.d(TAG, "无法发送视频:"+engine);
            }
            obj = null;
        } else {
            try {
                Thread.sleep(20l);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

最后,建一个线程发送:Thread mFrameThread=null;
mFrameThread=new Thread(frameRunnable);
                //mFrameThread.setPriority(4);
                mFrameThread.start();
在打开预览时就可以启动这个线程来发送图片了,在关闭预览时,结束发送线程。
在doubango中的ngn栈,它是使用c来处理这些东西的,对于 java,只需要调用jni方法直接把视频帧传入即可,没有这么多的缓存处理(有可能是在c端处理的,没有缓存是不行地)。

分享到:
评论
2 楼 phenom 2012-11-19  
完整的代码不能给,这个主要说明,在预览后,将帧保存起来,在一个列表中,然后另启动一个线程来发送帧(包含压缩,编码,发送,这部分由c处理。)。
如果在onPreviewFrame里面直接操作这些,这是在ui线程的。
在H264Android中有示例,作用是类似的,就是将预览的帧保存,供x264编码, 这个代码可以给。可以在h264编译这篇文章找到x264编码的代码,但里面的AndroidVideo.java在这里更新
ihopethatwell 写道
楼主,这个事例有完整demo?看着还是不是很明白

1 楼 ihopethatwell 2012-11-19  
楼主,这个事例有完整demo?看着还是不是很明白

相关推荐

Global site tag (gtag.js) - Google Analytics