同时获取Camera预览和录像视频流

来源:互联网 发布:检测电脑硬件的软件 编辑:程序博客网 时间:2024/06/05 14:17

介绍除了默认的Preview数据流之外,增加录像数据流。要求同时获取,并能够支持不同的分辨率。

1. 描述

高通平台支持同时获取Preview和录像,要获取双路视频流的话,除了Android系统默认支持获取Preview的数据流外,只需要在软件层面增加获取录像数据流的操作。以下详细介绍如何获取录像的数据流,Preview不做介绍。

2. 分析

首先,camera AF层基本的框架图如下:
这里写图片描述
Camera的录像的数据流程如下:
这里写图片描述

可以看出,录像的数据由HAL层回调到Framework层的Camera.cpp中,但并没有将数据丢到对应的Camera.java中,而是丢到CameraSource中,然后OMX Codec通过CameraSource获取数据,直接进行编码存储。
通过Preview和录像的数据流程可以看出,两路数据都会由HAL层回调到在Framework层的Camera.cpp中,对APP 层来说,此处就是数据的源头。由于Android中录像是通过MediaRecorder实现的,但是真正是由Stagefright框架来完成的,MediaRecorder并不直接接触到数据。如果通过MediaRecorder实现数据获取改动较大,不易完成。基于Camera现有的获取Preview数据流的回调机制,再增加录像数据流的回调会更方便。

3. 解决过程

1 新增接口

根据图2-1,Camera.java中没有直接录像的对外接口,所以我们需要添加录像的相关接口如下:

public native final void startRecording() throws IOException;public native final void stopRecording();

都是本地方法,需要在android_hardware_Camera.cpp中添加映射如下:

{ "startRecording",  "()V",   (void *)android_hardware_Camera_startRecording },{ "stopRecording",   "()V",   (void *)android_hardware_Camera_stopRecording },

以及方法如下:

static void android_hardware_Camera_startRecording(JNIEnv *env, jobject thiz){    ALOGV("startPreview in android_hardware_Camera_startRecording");    sp<Camera> camera = get_native_camera(env, thiz, NULL);    if (camera == 0) return;    if (camera->startRecording() != NO_ERROR) {        jniThrowRuntimeException(env, "startRecording failed");        return;    }}static void android_hardware_Camera_stopRecording(JNIEnv *env, jobject thiz){    ALOGV("startPreview in android_hardware_Camera_stopRecording");    sp<Camera> camera = get_native_camera(env, thiz, NULL);    if (camera == 0) return;    camera->stopRecording(); }

由于Camera.cpp已经有录像的接口,所以到此就可以通过Camera.java直接通过StartRecording()方法来启动Camera进行录像了。

接下来要在Camera.java中添加回调函数的接口,如下:

public interface VideoDataCallback{    void onVideoFrame(byte[] data, Camera camera);}

添加注册回调函数的接口:

public final void setVideoCallbackWithBuffer(VideoDataCallback vCb) {    mVideoDataCallback = vCb;}

在处理JNI层的消息的Handler中添加处理video数据的回调处理:

private class EventHandler extends Handler{    @Override    public void handleMessage(Message msg) {        switch(msg.what) {        case CAMERA_MSG_VIDEO_FRAME:            Log.d(TAG, "CAMERA_MSG_VIDEO_FRAME " + msg.arg1);            VideoDataCallback vCb = mVideoDataCallback;            if (vCb != null) {                vCb.onVideoFrame((byte[])msg.obj, mCamera);            }            return;        }    }}

需要在android_hardware_camera.cpp中添加存放video buffer的容器:

Vector<jbyteArray> mVideoCallbackBuffers;

添加创建录像视频流buffer的接口:

public final void addVideoCallbackBuffer(byte[] callbackBuffer){    _addCallbackBuffer(callbackBuffer, CAMERA_MSG_VIDEO_FRAME);}

对应的,需要在android_hardware_camera.cpp中将buffer放进容器供数据上来进行回调:

void JNICameraContext::addCallbackBuffer(JNIEnv *env, jbyteArray cbb, int msgType){    if (cbb != NULL) {        switch (msgType) {            case CAMERA_MSG_VIDEO_FRAME: {                jbyteArray callbackBuffer = (jbyteArray)env->NewGlobalRef(cbb);                mVideoCallbackBuffers.push(callbackBuffer);                break;}        }    }}

由于没有通过MediaRecorder启动Camera进行录像,根据代码,Camera会次优的将数据回调给android_hardware_camera.cpp,所以在android_hardware_camera.cpp向camera.java传递数据的地方添加处理video数据的代码:

void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType){    if (heapBase != NULL) {        const jbyte* data = reinterpret_cast<const jbyte*>(heapBase + offset);        if (msgType == CAMERA_MSG_VIDEO_FRAME){            obj = getCallbackBuffer(env, &mVideoCallbackBuffers, size);            if(mVideoCallbackBuffers.isEmpty()) {                ALOGV("  mVideoCallbackBuffers is NULL ");                if (obj == NULL) {                    ALOGV("  obj is NULL ");                    return;}            }        }    }    // post image data to Java    env->CallStaticVoidMethod(mCameraJClass, fields.post_event,            mCameraJObjectWeak, msgType, 0, 0, obj);}

2 完整的步骤

到此,按照以下步骤就可以获取到video数据了,获得的是每一帧的比特数据。
1, 实现VideoDataCallback接口
2, 使用Camera.java的setVideoCallbackWithBuffer()将回调函数注册到Camera.java中
3, 使用Camera.java的addVideoCallbackBuffer()将你当前数据流每一帧的大小设置给
android_hardware_camera.cpp
4, 使用Camera.java的startRecording()。

3 遇到的问题

1, 没有获取到数据。
原因:根据步骤的第二步,我们需要将每一帧的大小设置下去才可以准确的获得数据的回调。
根据之前的理解,录像的视频格式是NV12SP,这种格式每一帧的大小是Width*Height*3/2。分辨率按1920*1080,应该是3110400,但查看log时发现HAL层实际分配的每一帧大小是3137536。
解决方法:手动将buffer大小设置成3137536,发现获取数据成功。

2, 只能获取8帧的数据,便再也获取不到数据。
原因:调查log以及根据Camera框架处理回调buffer的机制发现,由于HAL层是直接将数据流以内存指针的方式回调上来,所以需要及时的将用过的内存释放掉。
解决方法:在Camera.cpp中添加释放已经处理过的内存释放掉。

3, 设置分辨率。
添加设置分辨率的接口。目前视频录像支持的分辨率如下:
1920x1080,1280x720,864x480,800x480,720x480,640x480,480x320,352x288,320x240,176x144

4,图像有绿线。
获取的图像如下:
这里写图片描述
原因:我们原以为Camera录像的数据格式是NV12SP,按照其存储格式,我们的存储方式是将APP层获得的数据从 0 - Width * Height 大小保存为Y分量,紧接着后面剩下的数据保
存为UV分量。这样保存的文件格式图像上方有细长的绿线。调查后发现真实的数据格
式是NV12SP32M,这种存储格式要求Y分量向128*32对齐,UV分量128*16对齐再加
上4096,整体的大小再保持4096的倍数。这样就导致如果Y分量的大小不对齐128*32,
后面紧跟着会填充空数据,而不是UV的数据。所以我们之前的存储方式会将后面的空
数据保存下来,反而漏掉一部分UV分量。

解决方法:按照分辨率大小,计算出 Y 和 UV 位置和大小,再保存就是真实有效的数据。

5, 设置每一帧数据大小
原因:由于一开始对视频流的格式判断错误,对每一帧的大小不好计算。现在知道正确的存储格式就可以计算出不同分辨率的图片每一帧的大小。
解决方案:根据图片存储格式计算。

4. 结果

最后获取的数据如下(只保存一帧):
这里写图片描述

0 0
原创粉丝点击