[Camera專題]Qcom- Video流異步處理并回調(diào)

1.前言

我們?cè)谧鰁is算法集成的時(shí)候,preview流或者video流需要經(jīng)過算法的異步處理,在hal1框架上實(shí)現(xiàn)就稍微有些難度,hal3或者camX相對(duì)要容易一些指孤。
如何去優(yōu)雅的實(shí)現(xiàn)異步回調(diào)數(shù)據(jù)呢?


  • 異步:前面幾幀贬堵,比如5幀video數(shù)據(jù)需要先給算法處理恃轩,當(dāng)?shù)?幀數(shù)據(jù)過來時(shí),算法處理好第1幀返回給系統(tǒng)黎做,以此類推叉跛。
  • 同步:一幀數(shù)據(jù)過來,算法處理完蒸殿,直接返回給系統(tǒng)筷厘。

2.獲取video數(shù)據(jù)的接口

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

void QCamera2HardwareInterface::video_stream_cb_routine(mm_camera_super_buf_t *super_frame,
                                                        QCameraStream *stream,
                                                        void *userdata)
{

  //1.拿到 QCamera2HardwareInterface指針
  QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
  //2.拿到視頻幀
  mm_camera_buf_def_t *frame = super_frame->bufs[0];
  //3.視頻幀的一些處理
  if (frame->buf_type == CAM_STREAM_BUF_TYPE_MPLANE) {
    if (pme->mParameters.getVideoBatchSize() == 0) {//一定會(huì)跑到這里面
        //計(jì)算視頻幀的時(shí)間戳
        timeStamp = nsecs_t(frame->ts.tv_sec) * 1000000000LL + frame->ts.tv_nsec;
        pme->dumpFrameToFile(stream, frame, QCAMERA_DUMP_FRM_VIDEO);
        videoMemObj = (QCameraVideoMemory *)frame->mem_info;
        video_mem = NULL;
        if (NULL != videoMemObj && !(pme->m_bNeedVideoCb)) {
          video_mem = videoMemObj->getMemory(frame->buf_idx,
              (pme->mStoreMetaDataInFrame > 0)? true : false);
          triggerTCB = TRUE;
          LOGH("Video frame TimeStamp : %lld batch = 0 idx = %d",
              timeStamp, frame->frame_idx);
        }
        if (pme->m_bNeedVideoCb) {
          video_mem = pme->videoMemFb->getMemory(frame->buf_idx,
              (pme->mStoreMetaDataInFrame > 0)? true : false);
          triggerTCB = TRUE;
        }

    }else{
      ···
    }
  
  }else{
    ···
  }
    //4.把數(shù)據(jù)傳遞給 視頻編碼器 處理
    if ((NULL != video_mem) && (triggerTCB == TRUE)) {
        if ((pme->mDataCbTimestamp != NULL) &&
            pme->msgTypeEnabledWithLock(CAMERA_MSG_VIDEO_FRAME) > 0) { 
            qcamera_callback_argm_t cbArg;
            memset(&cbArg, 0, sizeof(qcamera_callback_argm_t));
            cbArg.cb_type = QCAMERA_DATA_TIMESTAMP_CALLBACK;
            cbArg.msg_type = CAMERA_MSG_VIDEO_FRAME;
            cbArg.data = video_mem;

            // For VT usecase, ISP uses AVtimer not CLOCK_BOOTTIME as time source.
            // So do not change video timestamp.
            if (!pme->mParameters.isAVTimerEnabled()) {
                // Convert Boottime from camera to Monotime for video if needed.
                // Otherwise, mBootToMonoTimestampOffset value will be 0.
                timeStamp = timeStamp - pme->mBootToMonoTimestampOffset;
            }    
            LOGD("Final video buffer TimeStamp : %lld ", timeStamp);
            cbArg.timestamp = timeStamp;
            int32_t rc = pme->m_cbNotifier.notifyCallback(cbArg);
            if (rc != NO_ERROR) {
                LOGE("fail sending data notify");
                stream->bufDone(frame->buf_idx);
            }    
        }    
    }    
    if (!pme->mParameters.isVideoFaceBeautification()) {
        free(super_frame);
    }
}

函數(shù)作用:處理視頻流中的視頻幀

視頻幀將被發(fā)送到視頻編碼器。一旦視頻編碼器完成了視頻幀的處理宏所,它將調(diào)用另一個(gè)API (release_recording_frame)來返回幀酥艳,需要注意釋放super_frame。

3.異步回調(diào)的實(shí)現(xiàn)

想要優(yōu)雅的實(shí)現(xiàn)異步回調(diào)爬骤,那就需要利用c++ 11的新特性充石,簡(jiǎn)直好用到哭!
這里我們編寫一個(gè)簡(jiǎn)單的demo霞玄,完成異步回調(diào)的實(shí)現(xiàn)骤铃。

3.1 編寫Demo類

3.1.1 DemoCamera.h

#ifndef DEMOCAMETA_H_
#define DEMOCAMETA_H_

#include <functional>
#include <queue>
#include <pthread.h>

#include "QCamera2HWI.h"
#include "QCameraStream.h"

//自定義命名空間
namespace democamera {
namespace hal1 {

//封裝video數(shù)據(jù)
struct VideoData{
        mm_camera_super_buf_t *super_frame;
        qcamera::QCameraStream *stream;
        qcamera::QCamera2HardwareInterface *pme;
        std::function<void(int)> cbHandler;
};
//demo類
class DemoCamera{

public:
    DemoCamera(int);//構(gòu)造函數(shù) 
    virtual ~DemoCamera();//析構(gòu)函數(shù)
    //模擬算法數(shù)據(jù)處理
    void processVideo(
        mm_camera_super_buf_t *super_frame,
        qcamera::QCameraStream *stream,
        qcamera::QCamera2HardwareInterface *pme,
        std::function<void(int)> cbHandler);
        void callBack(void);
public:
    int videoFrame_Size;//需要的video幀數(shù)
    std::queue<VideoData> videoDataQ;//緩存video的隊(duì)列
    bool isStartVideo;//開始錄制
    bool isStopVideo;//停止錄制
};

}
}
#endif

3.1.2 DemoCamera.cpp

#define LOG_TAG "DemoCamera"

#include "DemoCamera.h"

extern "C" {
#include "mm_camera_dbg.h"
}

using namespace qcamera;
namespace democamera {
namespace hal1 {

DemoCamera::DemoCamera(int size)
{
    videoFrame_Size = size;//算法需要的video幀數(shù)量
    isStartVideo = 0;//是否開始錄制視頻
    isStopVideo = 0;//是否停止錄制視頻
    LOGE("videoFrame_Size =%d ",videoFrame_Size );
}
DemoCamera::~DemoCamera()
{
    video_Size = 0;//這里取釋放一些資源
    LOGE("video_Size=%d ",video_Size);
}

void DemoCamera::processVideo(mm_camera_super_buf_t *super_frame,QCameraStream *stream,
    QCamera2HardwareInterface *pme,std::function<void(int)> cbHandler)
{
    VideoData d;
    d.super_frame = super_frame;
    d.stream = stream;
    d.pme = pme;
    d.cbHandler = cbHandler;
    videoDataQ.push(d);//把video的數(shù)據(jù)存儲(chǔ)到隊(duì)列里
    //調(diào)用算法處理
    //xxx算法處理視頻幀
    //調(diào)用回調(diào)函數(shù)
    callBack();
}

void DemoCamera::callBack()
{
    if(!videoDataQ.empty())
    {
        //如果開始錄制了視頻拉岁,并且收到的視頻幀達(dá)到算法需要的幀數(shù)
        if(isStartVideo && (videoFrame_Size == videoDataQ.size())){
            VideoData d = videoDataQ.front();
            mm_camera_buf_def_t *frame = d.super_frame->bufs[0];
            LOGE("zcf_cb:call cbHandler!videoDataQ.size = %d frame->idx=%d ",videoDataQ.size(),frame->frame_idx);
            std::function<void(int)> cbHandler =  d.cbHandler;//拿出回調(diào)函數(shù)
            cbHandler(0);//回調(diào)給系統(tǒng)
            videoDataQ.pop();//將數(shù)據(jù)彈出隊(duì)列
        }
        //停止錄制時(shí),回調(diào)所有數(shù)據(jù)
        if(isStopVideo){
            LOGE("zcf_cb:錄像停止 吐出所有數(shù)據(jù) ");
            while(!videoDataQ.empty()){
            VideoData d = videoDataQ.front();
            mm_camera_buf_def_t *frame = d.super_frame->bufs[0];
            LOGE("zcf_cb:call cbHandler惰爬!videoDataQ.size = %d frame->idx=%d ",videoDataQ.size(),frame->frame_idx);
            std::function<void(int)> cbHandler =  d.cbHandler;
            cbHandler(0);
            videoDataQ.pop();
            }
        }
    }
}

}
}

3.2 DemoCamera加入編譯

hardware/qcom/camera/QCamera2/Android.mk

-LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_CFLAGS := -Wall -Wextra//防止 定義的變量不使用時(shí)會(huì)報(bào)錯(cuò)喊暖,
LOCAL_SRC_FILES += \
         util/QCameraExtZoomTranslator.cpp \
         util/QCameraPprocManager.cpp \
         util/QCameraBokeh.cpp \
         util/QCameraClearSight.cpp \
+        HAL/DemoCamera.cpp

3.3 初始化 DemoCamera

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

 #include "QCameraTrace.h"
 //前置聲明DemoCamera類
+namespace democamera {
+namespace hal1 {
+  class DemoCamera;
+}
+}
 namespace qcamera {
···
 private:
      //使用智能指針 防止內(nèi)存泄漏
+    std::unique_ptr<xtccamera::hal1::DemoCamera> mDemoCamera; 
     camera_device_t   mCameraDevice;
     uint32_t          mCameraId;
     mm_camera_vtbl_t *mCameraHandle;

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

 #include "QCameraTrace.h"
 #include "QCameraDisplay.h"
 
+#include "DemoCamera.h"

int QCamera2HardwareInterface::start_recording(struct camera_device *device)
{
+       hw->mDemoCamera->isStartVideo = 1;
+       hw->mDemoCamera->isStopVideo = 0;
}

int QCamera2HardwareInterface::stop_recording(struct camera_device *device)
{
+       hw->mDemoCamera->isStartVideo = 0;
+       hw->mDemoCamera->isStopVideo = 1;
}

int QCamera2HardwareInterface::openCamera(struct hw_device_t **hw_device)
{
    //初始化智能指針mDemoCamera
+   mDemoCamera.reset(new democamera::hal1::DemoCamera(5));//這里設(shè)置算法需要的幀數(shù)為5
    // Init params in the background
    // 1. It's safe to queue init job, even if alloc job is not yet complete.
    // It will be queued to the same thread, so the alloc is guaranteed to
    // finish first.
    // 2. However, it is not safe to begin param init until after camera is
    // open. That is why we wait until after camera open completes to schedule
    // this task.
    memset(&args, 0, sizeof(args));
    mParamInitJob = queueDeferredWork(CMD_DEF_PARAM_INIT, args);
}

3.4 調(diào)用算法處理Video流

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

 #include <stdlib.h>
+#include <functional>
···
+#include "DemoCamera.h"

void QCamera2HardwareInterface::video_stream_cb_routine(mm_camera_super_buf_t *super_frame,
                                                        QCameraStream *stream,
                                                        void *userdata)
{
···
  //1.拿到 QCamera2HardwareInterface指針
  QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
  //2.拿到視頻幀
  mm_camera_buf_def_t *frame = super_frame->bufs[0];
···
    //打印幀信息
++    LOGE("zcf_c:Stream(%d), Timestamp: %ld %ld frame->idx=%d frame->buf_type=%d,getVideoBatchSize=%d",
          frame->stream_id,
          frame->ts.tv_sec,
          frame->ts.tv_nsec,
          frame->frame_idx,
          frame->buf_type,
          pme->mParameters.getVideoBatchSize());
    //3.視頻幀的一些處理
    if (frame->buf_type == CAM_STREAM_BUF_TYPE_MPLANE) {
        if (pme->mParameters.getVideoBatchSize() == 0) { 

        //這里使用lamda表達(dá)式封裝callBackHandler,然后賦值給包裝器std::function<void(int)>
++          std::function<void(int)> callBackHandler =  [=](int result)mutable{

            timeStamp = nsecs_t(frame->ts.tv_sec) * 1000000000LL
                    + frame->ts.tv_nsec;
            pme->dumpFrameToFile(stream, frame, QCAMERA_DUMP_FRM_VIDEO);
            videoMemObj = (QCameraVideoMemory *)frame->mem_info;
            video_mem = NULL;
            if (NULL != videoMemObj && !(pme->m_bNeedVideoCb)) {
                video_mem = videoMemObj->getMemory(frame->buf_idx,
                        (pme->mStoreMetaDataInFrame > 0)? true : false);
                triggerTCB = TRUE;
                LOGH("ideo frame TimeStamp : %lld batch = 0 idx = %d",
                        timeStamp, frame->frame_idx);
            }
            if (pme->m_bNeedVideoCb) {
                video_mem = pme->videoMemFb->getMemory(frame->buf_idx,
                    (pme->mStoreMetaDataInFrame > 0)? true : false);
                triggerTCB = TRUE;
            }
              //4.把數(shù)據(jù)傳遞給 視頻編碼器 處理补鼻,這個(gè)直接封裝在callBackHandler里面
                if (!result) {
                    /**
                        Code is copied from bottom of the outer function (QCamera2HardwareInterface::video_stream_cb_routine)
                        so the code will be executed here in this callback instead
                    */
                    LOGE("zcf_cf: result =%d frame->idx=%d video_mem=%p triggerTCB=%d",
                        result,frame->frame_idx,video_mem,triggerTCB);
                    if ((NULL != video_mem) && (triggerTCB == TRUE)) {
                        if ((pme->mDataCbTimestamp != NULL) && pme->msgTypeEnabledWithLock(CAMERA_MSG_VIDEO_FRAME) > 0) {
                            qcamera_callback_argm_t cbArg;
                            memset(&cbArg, 0, sizeof(qcamera_callback_argm_t));
                            cbArg.cb_type = QCAMERA_DATA_TIMESTAMP_CALLBACK;
                            cbArg.msg_type = CAMERA_MSG_VIDEO_FRAME;
                            cbArg.data = video_mem;

                            // For VT usecase, ISP uses AVtimer not CLOCK_BOOTTIME as time source.
                            // So do not change video timestamp.
                            if (!pme->mParameters.isAVTimerEnabled()) {
                                // Convert Boottime from camera to Monotime for video if needed.
                                // Otherwise, mBootToMonoTimestampOffset value will be 0.
                                timeStamp = timeStamp - pme->mBootToMonoTimestampOffset;
                            }
                            LOGE("zcf_cf: Final video buffer TimeStamp : %lld frame->idx=%d", timeStamp,frame->frame_idx);
                            cbArg.timestamp = timeStamp;
                            int32_t rc = pme->m_cbNotifier.notifyCallback(cbArg);
                            if (rc != NO_ERROR) {
                                LOGE("fail sending data notify");
                                stream->bufDone(frame->buf_idx);
                            }
                        }
                    }
                }
                free(super_frame);//釋放資源
            };
            pme->mXTCCamera->processVideo(super_frame,stream,pme,callBackHandler);
            return;//這里return掉 不去調(diào)用步驟4 把數(shù)據(jù)傳遞給 視頻編碼器 處理
        }

}

3.5 編譯運(yùn)行

  • mmm hardware/qcom/camera/QCamera2/
    在out/···/vendor/lib/下會(huì)生成camera.msm8937.so
  • adb push camera.msm8937.so vendor/lib
  • 重啟驗(yàn)證 或者殺掉camera provider

log如下:

從log中可以看到哄啄,前面累計(jì)的5幀數(shù)據(jù)都沒有直接回調(diào)給系統(tǒng)雅任,而是等算法處理后风范,第3幀數(shù)據(jù)才回調(diào)給系統(tǒng)。

3.6 問題:最后幾幀沒有正確回調(diào)

//步驟4:把數(shù)據(jù)傳遞給 視頻編碼器 處理
 if ((NULL != video_mem) && (triggerTCB == TRUE)) {
    if ((pme->mDataCbTimestamp != NULL) && pme->msgTypeEnabledWithLock(CAMERA_MSG_VIDEO_FRAME) > 0) {
    ···
  }
}

添加log 重新編譯分析:
最后幾幀數(shù)據(jù)
pme->msgTypeEnabledWithLock(CAMERA_MSG_VIDEO_FRAME) =0
因此沒有正確回調(diào)


解決辦法

最簡(jiǎn)單的辦法就是去掉判斷pme->msgTypeEnabledWithLock(CAMERA_MSG_VIDEO_FRAME) > 0

可以看到log正郴γ矗回調(diào)了硼婿。
當(dāng)然這種解法不一定是最好的,畢竟修改了系統(tǒng)原有的邏輯禽车。
那么還有更好的解決方案嗎寇漫?

Stay hungry,Stay foolish殉摔!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末州胳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逸月,更是在濱河造成了極大的恐慌栓撞,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碗硬,死亡現(xiàn)場(chǎng)離奇詭異瓤湘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)恩尾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門弛说,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翰意,你說我怎么就攤上這事木人。” “怎么了冀偶?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵虎囚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蔫磨,道長(zhǎng)淘讥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任堤如,我火速辦了婚禮蒲列,結(jié)果婚禮上窒朋,老公的妹妹穿的比我還像新娘。我一直安慰自己蝗岖,他們只是感情好侥猩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抵赢,像睡著了一般欺劳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上铅鲤,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天划提,我揣著相機(jī)與錄音,去河邊找鬼邢享。 笑死鹏往,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骇塘。 我是一名探鬼主播伊履,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼款违!你這毒婦竟也來了唐瀑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤插爹,失蹤者是張志新(化名)和其女友劉穎哄辣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體递惋,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柔滔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了萍虽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睛廊。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖杉编,靈堂內(nèi)的尸體忽然破棺而出超全,到底是詐尸還是另有隱情,我是刑警寧澤邓馒,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布嘶朱,位于F島的核電站,受9級(jí)特大地震影響光酣,放射性物質(zhì)發(fā)生泄漏疏遏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望财异。 院中可真熱鬧倘零,春花似錦、人聲如沸戳寸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疫鹊。三九已至袖瞻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拆吆,已是汗流浹背聋迎。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锈拨,地道東北人砌庄。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓羹唠,卻偏偏與公主長(zhǎng)得像奕枢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子佩微,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容