高通sdm660android9.0平台camera HAL1中获取并修改预览数据

前言:

最近在做一个有关于HDMI IN的需求,需要找到传入数据的buffer地址,方便以后如果有修改数据的需求的话,可以快速实现,参考了很多人的博客,在此记录一下寻找的过程,一点愚见,希望大家多多指教。

手上平台使用的是HAL1。

参考:

android Camera API1+HAL1 open camera流程 & Android M_徐而思齐的博客-CSDN博客

android camera mmap,Android Camera 流程学习记录(四)—— Camera.startPreview() flow_Robby Robby的博客-CSDN博客

【Camera专题】HAL层-深入浅出startPreview - 简书

Camera HAL(Camera Preview)_honghong96的博客-CSDN博客_camera preview

Android4.4 Camera callback注册和回调过程分析_哇小明的博客-CSDN博客_callback接口

高通camera框架之如何打通App-Hardware经络_简一商业的博客-CSDN博客

分析:

硬件上是通过一个ic芯片将HDMI IN数据流转换成MIPI CSI,再一步步传输给上层;所以对于camera HAL来说,它在获取HDMI IN的数据和摄像头的数据所经历的过程是一样的。

由于HDMI IN的功能是通过打开应用camera预览来进行显示的,这里从camera的预览作为切入点:

追踪 Camera.startPreview() 方法:

以下为摘抄内容:

1. Frameworks

1.1 Camera.java位置:frameworks/base/core/java/android/hardware/Camera.java

startPreview():

给上层 application 提供一个接口。

进入 Runtime 层。

2. Android Runtime

2.1 android_hardware_Camera.cpp

位置:frameworks/base/core/jni/android_hardware_Camera.cpp

android_hardware_Camera_startPreview():

调用 get_native_camera() 函数获取一个 Camera 实例。

调用 Camera::startPreview()。

static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz)
{
    ALOGV("startPreview");
    sp camera = get_native_camera(env, thiz, NULL);
    if (camera == 0) return;
    if (camera->startPreview() != NO_ERROR) {
        jniThrowRuntimeException(env, "startPreview failed");
        return;
    }
}

3.1 Camera.cpp

位置:frameworks/av/camera/Camera.cpp

startPreview():

mCamera 便是在 connect 过程当中返回的 CameraClient,它具体实现了 startPreview() 接口。

调用 CameraClient::startPreview()。

// start preview mode
status_t Camera::startPreview()
{
    ALOGV("startPreview");
    sp <::android::hardware::icamera> c = mCamera;
    if (c == 0) return NO_INIT;
    return c->startPreview();
}

3.2 CameraClient.cpp

位置:frameworks/av/services/camera/libcameraservice/api1/CameraClient.cpp

startPreview():

经过 startCameraMode 函数进入具体的实现逻辑。

// start preview mode
status_t CameraClient::startPreview() {
    LOG1("startPreview (pid %d)", getCallingPid());
    return startCameraMode(CAMERA_PREVIEW_MODE);
}

经过 startCameraMode 函数进入具体的实现逻辑。

// start preview or recording
status_t CameraClient::startCameraMode(camera_mode mode) {
    LOG1("startCameraMode(%d)", mode);
    Mutex::Autolock lock(mLock);
    status_t result = checkPidAndHardware();
    if (result != NO_ERROR) return result;


    switch(mode) {
        case CAMERA_PREVIEW_MODE:
            if (mSurface == 0 && mPreviewWindow == 0) {
                LOG1("mSurface is not set yet.");
                // still able to start preview in this case.
            }
            return startPreviewMode();
        case CAMERA_RECORDING_MODE:
            if (mSurface == 0 && mPreviewWindow == 0) {
                ALOGE("mSurface or mPreviewWindow must be set before startRecordingMode.");
                return INVALID_OPERATION;
            }
            return startRecordingMode();
        default:
            return UNKNOWN_ERROR;
    }
}

根据传入的参数 CAMERA_PREVIEW_MODE 肯定进入的分支。

调用 startPreviewMode() 。

status_t CameraClient::startPreviewMode() {
    LOG1("startPreviewMode");
    status_t result = NO_ERROR;

    // if preview has been enabled, nothing needs to be done
    if (mHardware->previewEnabled()) {
        return NO_ERROR;
    }

    if (mPreviewWindow != 0) {
        mHardware->setPreviewScalingMode(
            NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
        mHardware->setPreviewTransform(mOrientation);
    }
    mHardware->setPreviewWindow(mPreviewWindow);
    result = mHardware->startPreview();
    if (result == NO_ERROR) {
        sCameraService->updateProxyDeviceState(
            hardware::ICameraServiceProxy::CAMERA_STATE_ACTIVE,
            mCameraIdStr, mCameraFacing, mClientPackageName,
            hardware::ICameraServiceProxy::CAMERA_API_LEVEL_1);
    }
    return result;
}

若是预览已经存在,则直接返回成功信息。

若是未存在,则继续往下走。

mHardware 是 CameraHardwareInterface 的实例,在 connect 过程的最后被初始化。

经过 mHardware 调用 setPreviewWindow() 和 startPreview() 接口。

进入 HAL 层。

4. HAL

4.1 CameraHardwareInterface.h

位置:frameworks/av/services/camera/libcameraservice/device1/CameraHardwareInterface.cpp

previewEnable():

经过 mDevice->ops 继续向下调用(不是咱们主要追踪的)。

mDevice 便是经过 hw_get_module() 相关流程进行初始化的设备实例,它的类型是 camera_device_t 。

若是 preview 存在,则返回 true 。

int CameraHardwareInterface::previewEnabled()
{
    ALOGV("%s(%s)", __FUNCTION__, mName.string());
    if (CC_LIKELY(mHidlDevice != nullptr)) {
        return mHidlDevice->previewEnabled();
    }
    return false;
}

setPreviewWindow(): 

如果 set_preview_window 函数指针为空,则返回失败信息。

若否,通过 mDevice->ops 继续向下调用(不是我们主要追踪的)。

 

应用启动时,会设置预览窗口,对应的就是这里的mPreviewWindow。

后续调用HAL的startPreview方法!

这里的mHardware = new CameraHardwareInterface(camera_device_name);

startPreview():

若 start_preview() 函数指针为空,则返回失败信息。

若否,则经过 mDevice 进行下一步操做。

关于 mDevice,咱们结合 Camera.open() 流程与 hw_get_module() 相关逻辑,能够知道它的逻辑是这样的:

在 CameraService 启动时,会调用 onFirstRef() 对 module 进行初始化,获取 module 实例。

在 open 过程当中,CameraClient 链接 CameraServer 成功时,会实例化 CameraHardwareInterface,并传入 module 实例对其初始化。

在初始化过程当中,经过 module 实例对应的 open 方法,咱们得到一个 device 实例,即 mDevice,这对应了具体的摄像头设备。

经过 mDevice,咱们就能够将对应的指令传达到硬件设备。

经过对 camera_device_t 类型进行追踪,能够找到函数指针的一个具体指向。

status_t CameraHardwareInterface::startPreview()
{
    ALOGV("%s(%s)", __FUNCTION__, mName.string());
    if (CC_LIKELY(mHidlDevice != nullptr)) {
        return CameraProviderManager::mapToStatusT(
                mHidlDevice->startPreview());
    }
    return INVALID_OPERATION;
}

hardware\interfaces\camera\device\1.0\default\CameraDevice.cpp

Return<Status> CameraDevice::startPreview() {
    ALOGV("%s(%s)", __FUNCTION__, mCameraId.c_str());
    Mutex::Autolock _l(mLock);
    if (!mDevice) {
        ALOGE("%s called while camera is not opened", __FUNCTION__);
        return Status::OPERATION_NOT_SUPPORTED;
    }
    if (mDevice->ops->start_preview) {
        return getHidlStatus(mDevice->ops->start_preview(mDevice));
    }
    return Status::INTERNAL_ERROR; // HAL should provide start_preview
}

这个 mDevice是在 CameraService 启动时,会调用 onFirstRef() 对 module 进行初始化,获取 module 实例。

在 open 过程当中,CameraClient 链接 CameraServer 成功时,会实例化 CameraHardwareInterface,并传入 module 实例对其初始化。

在初始化过程当中,经过 module 实例对应的 open 方法,咱们得到一个 device 实例,即 mDevice,这对应了具体的摄像头设备。

经过 mDevice,咱们就能够将对应的指令传达到硬件设备。

经过对 camera_device_t 类型进行追踪,能够找到函数指针的一个具体指向。

4.2 camera.h

位置:hardware/libhardware/include/hardware/camera.h

camera_device_ops_t

 

camera framwork的控制实现,以及数据回调_wing12345678910的博客-CSDN博客

 到了这里,我根据网上的分析去看了函数指针对应指向的文件:最终找到了

 

以下的线程分析部分是根据参考的博客摘抄过来的,实际过程中,我通过在QCamera2HWI.cpp里面搜索直接跳到了QCamera2HardwareInterface::startPreView()方法。

摘抄继续:分析到这里,就要用到我们C++的线程知识了

1.首先设置evt = QCAMERA_SM_EVT_START_PREVIEW,通过hw->processAPI(evt, NULL)发送事件。

其中m_stateMachine类型:QCameraStateMachine m_stateMachine;

调用procAPI,该函数作用:处理来自framew层的传入API请求。

hardware/qcom/camera/QCamera2/HAL/QCameraStateMachine.cpp

 

 

这里

node->cmd = QCAMERA_SM_CMD_TYPE_API;

node->evt = evt;

node->evt_payload = api_payload;

然后入队!

api_queue.enqueue((void *)node)

这里的线程就是CAM_stMachine,处理了入队,也会处理出队。

还记得我们3.3.1节中分析的

pthread_create创建线程,名称为:CAM_stMachine,线程函数smEvtProcRoutine。

我们来看这个线程函数:

3.3.1节就分析过,事件分2种:1.API事件,2.evt事件,优先处理API事件!

这里的TYPE为:QCAMERA_SM_CMD_TYPE_API

因此继续调用:

pme->stateMachine(node->evt, node->evt_payload);

 

这里根据m_sate来调用不同的函数,还记得吗,3.3.1节中分析的:

初始状态: m_state = QCAMERA_SM_STATE_PREVIEW_STOPPED;

因此会继续调用procEvtPreviewStoppedState(evt, payload);

 

int32_t QCameraStateMachine::procEvtPreviewStoppedState(qcamera_sm_evt_enum_t evt,
                                                        void *payload){
    int32_t rc = NO_ERROR;
    qcamera_api_result_t result;
    memset(&result, 0, sizeof(qcamera_api_result_t));


    switch (evt) {
    case QCAMERA_SM_EVT_SET_PREVIEW_WINDOW:
    ···
         break;
    case QCAMERA_SM_EVT_SET_CALLBACKS:
    ···
        break;
···省略多个case
    case QCAMERA_SM_EVT_START_PREVIEW:
        {
            if (m_parent->mPreviewWindow == NULL) {
                    ALOGE("%s: zcf mPreviewWindow == NULL,call preparePreview",__func__);
                rc = m_parent->preparePreview();//preview的准备工作
                if(rc == NO_ERROR) {//设置状态为QCAMERA_SM_STATE_PREVIEW_READY
                    ALOGE("%s: zcf preview window is not set yet, move to previewReady state",__func__);
                    // preview window is not set yet, move to previewReady state
                    m_state = QCAMERA_SM_STATE_PREVIEW_READY;
                } else {
                    ALOGE("%s: preparePreview failed",__func__);
                }
            } else {//我们基本走的是这个分支
                    ALOGE("%s: zcf mPreviewWindow != NULL,call preparePreview",__func__);
                rc = m_parent->preparePreview();//preparePreview准备工作
                if (rc == NO_ERROR) {
                    ALOGE("%s: zcf call startPreview",__func__);
                    rc = m_parent->startPreview();//调用startPreview
                    if (rc != NO_ERROR) {
                        m_parent->unpreparePreview();
                    } else {
                        // start preview success, move to previewing state
                        ALOGE("%s: zcf start preview success, move to previewing state",__func__);
                        m_state = QCAMERA_SM_STATE_PREVIEWING;
                    }
                }
            }
            result.status = rc;//返回结果
            result.request_api = evt;//
            result.result_type = QCAMERA_API_RESULT_TYPE_DEF;
            m_parent->signalAPIResult(&result);//这里去唤醒等待线程
        }
        break;
···
}

 

这里的 m_parent 就是 QCamera2HardwareInterface 实例对象(3.3.1节分析过)。

因而分别调用的是

QCamera2HardwareInterface::preparePreview()

QCamera2HardwareInterface::startPreview()

int32_t QCamera2HardwareInterface::preparePreview(){
    ATRACE_CALL();
    int32_t rc = NO_ERROR;


        CDBG_HIGH("%s: zcf E", __func__);
    //如果开启ZSL模式,且不是录像模式
    if (mParameters.isZSLMode() && mParameters.getRecordingHintValue() !=true) {
        rc = addChannel(QCAMERA_CH_TYPE_ZSL);//添加ZSL通道
        if (rc != NO_ERROR) {
            return rc;
        }
    } else {
        bool recordingHint = mParameters.getRecordingHintValue();
        if(recordingHint) {
            //stop face detection,longshot,etc if turned ON in Camera mode
            int32_t arg; //dummy arg#ifndef VANILLA_HAL
            if (isLongshotEnabled()) {
                sendCommand(CAMERA_CMD_LONGSHOT_OFF, arg, arg);
            }#endif
            if (mParameters.isFaceDetectionEnabled()) {
                sendCommand(CAMERA_CMD_STOP_FACE_DETECTION, arg, arg);
            }#ifndef VANILLA_HAL
            if (mParameters.isHistogramEnabled()) {
                sendCommand(CAMERA_CMD_HISTOGRAM_OFF, arg, arg);
            }#endif


            cam_dimension_t videoSize;
            mParameters.getVideoSize(&videoSize.width, &videoSize.height);
            if (!is4k2kResolution(&videoSize) && !mParameters.isLowPowerEnabled()) {
               rc = addChannel(QCAMERA_CH_TYPE_SNAPSHOT);
               if (rc != NO_ERROR) {
                   return rc;
               }
            }
            rc = addChannel(QCAMERA_CH_TYPE_VIDEO);
            if (rc != NO_ERROR) {
                delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
                return rc;
            }
        }
        //添加preview通道
        rc = addChannel(QCAMERA_CH_TYPE_PREVIEW);
        if (rc != NO_ERROR) {
            if (recordingHint) {
                delChannel(QCAMERA_CH_TYPE_SNAPSHOT);
                delChannel(QCAMERA_CH_TYPE_VIDEO);
            }
            return rc;
        }


        if (!recordingHint) {
            waitDefferedWork(mMetadataJob);
        }
    }


    CDBG_HIGH("%s: zcf X", __func__);
    return rc;}

最主要的是 rc = addChannel(QCAMERA_CH_TYPE_PREVIEW);添加Preview通道!

接下来调用QCamera2HardwareInterface::startPreview()

QCamera2HardwareInterface::startPreview()分析

hardware/qcom/camera/QCamera2/HAL/QCamera2HWI.cpp

 

兜兜转转,最后调用到

rc = startChannel(QCAMERA_CH_TYPE_PREVIEW);

通过该函数preview数据流

执行完QCamera2HardwareInterface::startPreview之后,

最终设置Camera状态:QCAMERA_SM_STATE_PREVIEWING

camera预览数据流代码流程_来自深圳的驴的博客-CSDN博客_camera stream

int32_t QCamera2HardwareInterface::startChannel(qcamera_ch_type_enum_t ch_type)
{
    int32_t rc = UNKNOWN_ERROR;
    if (m_channels[ch_type] != NULL) {
        rc = m_channels[ch_type]->start();
    }
    return rc;
}

hardware/qcom/camera/QCamera2/HAL/QCameraChannel.cpp

这张图片描述的很清楚了,channel里面的所有的stream都会start起来,每一个stream都会调用setbundleinfo和start函数。我们暂时先略过setbundleinfo,先看QCameraStream.cpp里面的start函数。

 mProcTh这个对象是QCameraThread这个类的对象

这里的start_routine实际上就是上面start函数传给的参数dataProcRoutine函数,我们接下来看这个函数的原型

pme->mDataCB(frame, pme, pme->mUserData);

——这个mDataCB()是在addPreviewChannel()->addStreamToChannel()的时候
                            //加的回调函数preview_stream_cb_routine !!!
                            //这个函数应该是处理并显示用的

【Camera】qcom-你应该掌握的camera数据流 - 简书

下面接着继续分析:

mm_camera_super_buf_t结构体:

[Camera]高通平台 camera 的 super buffer_armWing的博客-CSDN博客

super buffer 是高通hal1中常用的概念,对应的数据结构是:mm_camera_super_buf_t

typedef struct {
    uint32_t camera_handle;
    uint32_t ch_id;
    uint32_t num_bufs;
    uint8_t bUnlockAEC;
    uint8_t bReadyForPrepareSnapshot;
    mm_camera_buf_def_t* bufs[MAX_STREAM_NUM_IN_BUNDLE];
} mm_camera_super_buf_t;

 很明显mm_camera_super_buf_t 是由mm_camera_buf_def_t 组合而成。

其中 mm_camera_buf_def_t 是具体管理图像数据并且和v4l2交互的:

typedef struct mm_camera_buf_def {
    uint32_t stream_id;
    cam_stream_type_t stream_type;
    cam_stream_buf_type buf_type;
    uint32_t buf_idx;
    uint8_t is_uv_subsampled;
    struct timespec ts;
    uint32_t frame_idx;
    union {
        mm_camera_plane_buf_def_t planes_buf;
        mm_camera_user_buf_def_t user_buf;
    };
    int fd;
    void *buffer;
    size_t frame_len;
    void *mem_info;
    uint32_t flags;
    uint32_t cache_flags;
} mm_camera_buf_def_t;

stream 回调用来处理实时性较强的场景:如Preview。

2.channel 回调用来处理 需要从多个 stream 中取出数据的任务,例如:Snapshot 场景会用到图像数据和metadata 数据。

这些回调函数拿到 super buffer 后会进一步会取出自己需要的子buffer(也就是上文中提到的mm_camera_buf_def_t* bufs[])。例如 Preview 回调中:

void QCamera2HardwareInterface::preview_stream_cb_routine(mm_camera_super_buf_t *super_frame,
                                                          QCameraStream * stream,
                                                          void *userdata)
{
     ......
     /*只需要取到预览buffer bufs[0] 就可以*/
     QCameraGrallocMemory *memory = (QCameraGrallocMemory *)super_frame->bufs[0]->mem_info;
     ......
     mm_camera_buf_def_t *frame = super_frame->bufs[0];
     ......
}       

获取YUV图像信息和Buffer地址

高通(QCOM)平台HAL层获取预览/拍照/录像YUV数据 - 简书

知道在何处截取YUV数据后, 还需知道如何从高通定义的相关结构体中获取和YUV相关的信息和实际buffer地址.上面所说的函数中, 都有一个类型为mm_camera_super_buf_t*的结构体指针, 我们需要通过如下步骤来找到拍照实际YUV数据的buffer地址

1获取 QCameraChannel, 根据获取的数据类型不同, Channel也不同主要分为几种: QCAMERA_CH_TYPE_VIDEO(video), QCAMERA_CH_TYPE_SNAPSHOT(video snap), QCAMERA_CH_TYPE_ZSL(zsl), QCAMERA_CH_TYPE_CAPTURE(capture)

示例(zsl_channel_cb()中获取channel):

QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
QCameraChannel *pChannel = pme->m_channels[QCAMERA_CH_TYPE_ZSL];

2通过Channel获取QCameraStream和mm_camera_buf_def_t, 因为返回回来的数据可能有多帧(拍照的YUV, 缩略图等), 我们需要找到我们想要的数据, 示例代码如下:

    mm_camera_buf_def_t* yuvFrame = NULL;
    QCameraStream* stream = NULL;
    // frame 为 mm_camera_super_buf_t 类型
    for (uint32_t i = 0; i < frame->num_bufs; i++) {
        stream = pChannel->getStreamByHandle(frame->bufs[i]->stream_id);
        if (stream != NULL) {
            // 找到拍照数据
            if (stream->isOrignalTypeOf(CAM_STREAM_TYPE_SNAPSHOT)) {
                yuvFrame = frame->bufs[i];
                break;
            }
        }
    }

3通过步骤2基本就得到YUV数据了, YUV buffer地址为 yuvFrame->buffer以及buffer大小 yuvFrame->frame_len(这里就是我想要的地方!!)

4获取图像额外信息(宽, 高, 对齐后的宽高)

在高通平台, YUV数据一般会有对齐, 对齐是指为了处理效率更高, 图片宽高必须是某些数的整数倍(如 32或者64), 当然为什么对齐后处理效率更高, 这个好像是由于硬件设计的一些特性, 详细就不太清楚了. 如果图片宽高不是64位倍数, 对齐过后会在原图片右侧和下方留下无效像素, 当然经过JPEG硬件编码过后会被裁剪, 所以App层看到的是正常的, 只不过我们在HAL层获取的YUV数据是有无效像素的, 我们可以通过下面方法获取图片实际宽高和对齐后的宽高.

cam_frame_len_offset_t offset;memset(&offset, 0, sizeof(cam_frame_len_offset_t));
cam_dimension_t dim;memset(&dim, 0, sizeof(dim));//stream为步骤2中获取的QCameraStream*
stream->getFrameOffset(offset);
stream->getFrameDimension(dim);

图片实际宽为:dim.width, 高为:dim.height, 对齐后的宽为:offset.mp[0].stride, 高为:offset.mp[0].scanline

看到这里,可以确认dataProcRoutine就是我要找的地方了

我只需要接下来在这个地方,获取地址,以及将地址指向一张图片,看一下打开预览之后有没有显示图片就可以了。

//xyx:
#define LOG_NDEBUG 0
//xyx

void *QCameraStream::dataProcRoutine(void *data)
{
    int running = 1;
    int ret;
    QCameraStream *pme = (QCameraStream *)data;
    QCameraCmdThread *cmdThread = &pme->mProcTh;
    cmdThread->setName("CAM_strmDatProc");


    LOGD("E");
    do {
        do {
            ret = cam_sem_wait(&cmdThread->cmd_sem);
            if (ret != 0 && errno != EINVAL) {
                LOGE("cam_sem_wait error (%s)",
                       strerror(errno));
                return NULL;
            }
        } while (ret != 0);


        // we got notified about new cmd avail in cmd queue
        camera_cmd_type_t cmd = cmdThread->getCmd();
        switch (cmd) {
        case CAMERA_CMD_TYPE_DO_NEXT_JOB:
            {
                LOGD("Do next job");
                mm_camera_super_buf_t *frame =
                    (mm_camera_super_buf_t *)pme->mDataQ.dequeue();
                
                if (NULL != frame) {
                    //xyx:
                    mm_camera_buf_def_t *data_frame = frame->bufs[0];
                    //if (data_frame != NULL){
                        //Save final result
                        //修改数据前先保存一张图片
                        int file_fd_calc_before = 0;
                        file_fd_calc_before = open("/data/vendor/camera/lxx_HWI_before.yuv", O_RDWR | O_CREAT, 0777);
                        write(file_fd_calc_before,(uint8_t *)data_frame->buffer, data_frame->frame_len);
                        close(file_fd_calc_before);
                        
                        //获取buffer的宽高
                        cam_frame_len_offset_t offset;
                        memset(&offset, 0, sizeof(cam_frame_len_offset_t));
                        cam_dimension_t dim;
                        memset(&dim, 0, sizeof(dim));
                        
                        pme->getFrameOffset(offset);
                        pme->getFrameDimension(dim);
                        ALOGV("dataProcRoutine: dim.width = %d, dim.height = %d, offset.width = %d, offset.height = %d",
                                    dim.width, dim.height, offset.mp[0].stride, offset.mp[0].scanline);
                                    
                        //通过属性控制是否开启替换buffer数据
                        int32_t datareplace_enable = 0;
                        char datareplace[PROPERTY_VALUE_MAX];
                        char buf[64];
                        int file_fd = 0;
                        static int ret = 0;
                        property_get("persist.vendor.camera.datareplace.enable", datareplace, "0");
                        datareplace_enable = atoi(datareplace);    
                        
                        //通过上面的log可以知道buffer大小是1280*720,这里就准备一张1280*720的图片
                        //Push one Picture
                        snprintf(buf, sizeof(buf), "/data/vendor/camera/1280_720.yuv");
                        
                        if (datareplace_enable == 0){
                            file_fd = open(buf, O_RDWR | O_CREAT, 0777);
                            if(file_fd < 0)
                                ALOGV("xyx : preview: lxx cannot open fd ~~~~~~~~~~~~~");;
                            ret = read(file_fd,(uint8_t *)data_frame->buffer,(720*1280*3)/2);
                            if (ret < 0)
                                ALOGV("xyx : preview : lxx read failed ~~~~~~~~~~~~~");
                            close(file_fd);
                        }
                        
                        //替换完之后再保存一张图片
                        int file_fd_calc_after = 0;
                        file_fd_calc_after = open("/data/vendor/camera/lxx_HWI_after.yuv", O_RDWR | O_CREAT, 0777);
                        write(file_fd_calc_after,(uint8_t *)data_frame->buffer, data_frame->frame_len);
                        close(file_fd_calc_after);/**/
                    //}
                    //xyx
                    if (pme->mDataCB != NULL) {
                        pme->mDataCB(frame, pme, pme->mUserData);
                        //ALOGV("xyx : pme->mDataCB(frame, pme, pme->mUserData) is run here~~~~~~~~~~~~~");
                    } else {
                        // no data cb routine, return buf here
                        pme->bufDone(frame);
                        free(frame);
                    }
                }
            }
            break;
        case CAMERA_CMD_TYPE_EXIT:
            LOGH("Exit");
            /* flush data buf queue */
            pme->mDataQ.flush();
            running = 0;
            break;
        default:
            break;
        }
    } while (running);
    LOGH("X");
    return NULL;
}

保存出来的lxx_HWI_before.yuv图片格式是1280*720的,yuv_NV21格式的

自此,目的达成,写的有些乱,以后有时间再整理吧


版权声明:本文为weixin_51115260原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。