{"id":647,"date":"2013-07-08T19:40:06","date_gmt":"2013-07-08T11:40:06","guid":{"rendered":"http:\/\/guoh.ai\/lifelog\/?p=647"},"modified":"2013-07-09T07:18:57","modified_gmt":"2013-07-08T23:18:57","slug":"the-process-of-driver-data-shown-on-viewfinder","status":"publish","type":"post","link":"https:\/\/guoh.ai\/lifelog\/2013\/07\/the-process-of-driver-data-shown-on-viewfinder\/","title":{"rendered":"Driver\u56de\u6765\u7684\u6570\u636e\u662f\u5982\u4f55\u663e\u793a\u51fa\u6765\u7684"},"content":{"rendered":"<p>\u60f3\u8981\u77e5\u9053Driver\u56de\u6765\u7684\u6570\u636e\u662f\u5982\u4f55\u663e\u793a\u51fa\u6765\u7684\u5417\uff1f<br \/>\n\u8fd9\u4e2a\u5de5\u4f5c\u662f\u5728\u54ea\u4e00\u5c42\u505a\u7684\uff1f<\/p>\n<p>\u8ddf\u8e2a\u4ee3\u7801\u5c31\u53ef\u4ee5\u7406\u6e05\u695a\u8fd9\u4e00\u8fc7\u7a0b\uff0c\u8fd9\u91cc\u4ee3\u7801\u7248\u672c\u662fJelly Bean\u3002<\/p>\n<p>CameraHardwareInterface\u521d\u59cb\u5316\u7684\u65f6\u5019\u4f1a\u521d\u59cb\u5316\u597dwindow\u6709\u5173\u7684\u6570\u636e<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">struct camera_preview_window {\r\n    struct preview_stream_ops nw;\r\n    void *user;\r\n};\r\n\r\nstruct camera_preview_window mHalPreviewWindow;\r\n\r\nstatus_t initialize(hw_module_t *module)\r\n{\r\n    ALOGI(&quot;Opening camera %s&quot;, mName.string());\r\n    int rc = module-&gt;methods-&gt;open(module, mName.string(),\r\n                                   (hw_device_t **)&amp;mDevice);\r\n    if (rc != OK) {\r\n        ALOGE(&quot;Could not open camera %s: %d&quot;, mName.string(), rc);\r\n        return rc;\r\n    }\r\n    initHalPreviewWindow();\r\n    return rc;\r\n}\r\n\r\nvoid initHalPreviewWindow()\r\n{\r\n    mHalPreviewWindow.nw.cancel_buffer = __cancel_buffer;\r\n    mHalPreviewWindow.nw.lock_buffer = __lock_buffer;\r\n    mHalPreviewWindow.nw.dequeue_buffer = __dequeue_buffer;\r\n    mHalPreviewWindow.nw.enqueue_buffer = __enqueue_buffer;\r\n    mHalPreviewWindow.nw.set_buffer_count = __set_buffer_count;\r\n    mHalPreviewWindow.nw.set_buffers_geometry = __set_buffers_geometry;\r\n    mHalPreviewWindow.nw.set_crop = __set_crop;\r\n    mHalPreviewWindow.nw.set_timestamp = __set_timestamp;\r\n    mHalPreviewWindow.nw.set_usage = __set_usage;\r\n    mHalPreviewWindow.nw.set_swap_interval = __set_swap_interval;\r\n\r\n    mHalPreviewWindow.nw.get_min_undequeued_buffer_count =\r\n            __get_min_undequeued_buffer_count;\r\n}\r\n\r\nstatic int __dequeue_buffer(struct preview_stream_ops* w,\r\n                            buffer_handle_t** buffer, int *stride)\r\n{\r\n    int rc;\r\n    ANativeWindow *a = anw(w);\r\n    ANativeWindowBuffer* anb;\r\n    rc = native_window_dequeue_buffer_and_wait(a, &amp;anb);\r\n    if (!rc) {\r\n        *buffer = &amp;anb-&gt;handle;\r\n        *stride = anb-&gt;stride;\r\n    }\r\n    return rc;\r\n}\r\n\r\nstatic int __enqueue_buffer(struct preview_stream_ops* w,\r\n                  buffer_handle_t* buffer)\r\n{\r\n    ANativeWindow *a = anw(w);\r\n    return a-&gt;queueBuffer(a,\r\n              container_of(buffer, ANativeWindowBuffer, handle), -1);\r\n}<\/pre>\n<p>\u7ee7\u800c\u5728Vendor\u7684HAL\u5f53\u4e2d\uff0c\u8fd9\u91cc\u4ee5TI\u4e3a\u4f8b\u5b50<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\/**\r\n   @brief Sets ANativeWindow object.\r\n\r\n   Preview buffers provided to CameraHal via this object. DisplayAdapter will be interfacing with it\r\n   to render buffers to display.\r\n\r\n   @param&#x5B;in] window The ANativeWindow object created by Surface flinger\r\n   @return NO_ERROR If the ANativeWindow object passes validation criteria\r\n   @todo Define validation criteria for ANativeWindow object. Define error codes for scenarios\r\n\r\n *\/\r\nstatus_t CameraHal::setPreviewWindow(struct preview_stream_ops *window)\r\n{\r\n    status_t ret = NO_ERROR;\r\n    CameraAdapter::BuffersDescriptor desc;\r\n\r\n    LOG_FUNCTION_NAME;\r\n    mSetPreviewWindowCalled = true;\r\n\r\n   \/\/\/If the Camera service passes a null window, we destroy existing window and free the DisplayAdapter\r\n    if(!window)\r\n    {\r\n        if(mDisplayAdapter.get() != NULL)\r\n        {\r\n            \/\/\/NULL window passed, destroy the display adapter if present\r\n            CAMHAL_LOGDA(&quot;NULL window passed, destroying display adapter&quot;);\r\n            mDisplayAdapter.clear();\r\n            \/\/\/@remarks If there was a window previously existing, we usually expect another valid window to be passed by the client\r\n            \/\/\/@remarks so, we will wait until it passes a valid window to begin the preview again\r\n            mSetPreviewWindowCalled = false;\r\n        }\r\n        CAMHAL_LOGDA(&quot;NULL ANativeWindow passed to setPreviewWindow&quot;);\r\n        return NO_ERROR;\r\n    }else if(mDisplayAdapter.get() == NULL)\r\n    {\r\n        \/\/ Need to create the display adapter since it has not been created\r\n        \/\/ Create display adapter\r\n        mDisplayAdapter = new ANativeWindowDisplayAdapter();\r\n\r\n        ...\r\n\r\n        \/\/ DisplayAdapter needs to know where to get the CameraFrames from inorder to display\r\n        \/\/ Since CameraAdapter is the one that provides the frames, set it as the frame provider for DisplayAdapter\r\n        mDisplayAdapter-&gt;setFrameProvider(mCameraAdapter);\r\n\r\n        ...\r\n\r\n        \/\/ Update the display adapter with the new window that is passed from CameraService\r\n        \/\/ \u8fd9\u91cc\u7684window(ANativeWindowDisplayAdapter::mANativeWindow)\u5c31\u662fCameraHardwareInterface::mHalPreviewWindow::nw\r\n        ret  = mDisplayAdapter-&gt;setPreviewWindow(window);\r\n        if(ret!=NO_ERROR)\r\n            {\r\n            CAMHAL_LOGEB(&quot;DisplayAdapter setPreviewWindow returned error %d&quot;, ret);\r\n            }\r\n\r\n        if(mPreviewStartInProgress)\r\n        {\r\n            CAMHAL_LOGDA(&quot;setPreviewWindow called when preview running&quot;);\r\n            \/\/ Start the preview since the window is now available\r\n            ret = startPreview();\r\n        }\r\n    } else {\r\n        \/\/ Update the display adapter with the new window that is passed from CameraService\r\n        ret = mDisplayAdapter-&gt;setPreviewWindow(window);\r\n        if ( (NO_ERROR == ret) &amp;&amp; previewEnabled() ) {\r\n            restartPreview();\r\n        } else if (ret == ALREADY_EXISTS) {\r\n            \/\/ ALREADY_EXISTS should be treated as a noop in this case\r\n            ret = NO_ERROR;\r\n        }\r\n    }\r\n    LOG_FUNCTION_NAME_EXIT;\r\n\r\n    return ret;\r\n\r\n}<\/pre>\n<p>\u6ce8\u610f\u770bsetPreviewWindow\u4e4b\u524d\u6211\u52a0\u7684\u4e00\u884c\u4e2d\u6587\u6ce8\u91ca<\/p>\n<p>\u5728TI\u7684HAL\u5f53\u4e2d\u6709\u53bb\u505a\u5305\u4e00\u4e9badaper\u51fa\u6765\uff0c\u5b9e\u9645\u5c31\u662f\u53d6\u6570\u636e\u7684(\u6bd4\u5982V4L2CameraAdapter\uff0c\u4e5f\u5c31\u662fCameraAdapter)\uff0c\u548c\u6570\u636e\u53d1\u5176\u4ed6\u5730\u65b9(\u6bd4\u5982ANativeWindowDisplayAdapter\uff0c\u4e5f\u5c31\u662fDisplayAdapter)\u53bb\u7528\u7684\u3002<\/p>\n<p>\u5728TI\u7684HW\u5f53\u4e2d\uff0c\u6709previewThread(\u4f4d\u4e8eV4LCameraAdapter\u5f53\u4e2d)\uff0cpreviewThread\u4f1a\u4e0d\u65ad\u7684\u901a\u8fc7IOCTL\u4eceV4L2\u83b7\u53d6frame<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">int V4LCameraAdapter::previewThread()\r\n{\r\n    status_t ret = NO_ERROR;\r\n    int width, height;\r\n    CameraFrame frame;\r\n\r\n    if (mPreviewing)\r\n        {\r\n        int index = 0;\r\n        char *fp = this-&gt;GetFrame(index);\r\n\r\n        uint8_t* ptr = (uint8_t*) mPreviewBufs.keyAt(index);\r\n\r\n        int width, height;\r\n        uint16_t* dest = (uint16_t*)ptr;\r\n        uint16_t* src = (uint16_t*) fp;\r\n        mParams.getPreviewSize(&amp;width, &amp;height);\r\n\r\n        ...\r\n\r\n        mParams.getPreviewSize(&amp;width, &amp;height);\r\n        frame.mFrameType = CameraFrame::PREVIEW_FRAME_SYNC;\r\n        frame.mBuffer = ptr;\r\n\r\n        ...\r\n\r\n        ret = sendFrameToSubscribers(&amp;frame);\r\n\r\n        }\r\n\r\n    return ret;\r\n}\r\n\r\nchar * V4LCameraAdapter::GetFrame(int &amp;index)\r\n{\r\n    int ret;\r\n\r\n    mVideoInfo-&gt;buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;\r\n    mVideoInfo-&gt;buf.memory = V4L2_MEMORY_MMAP;\r\n\r\n    \/* DQ *\/\r\n    ret = ioctl(mCameraHandle, VIDIOC_DQBUF, &amp;mVideoInfo-&gt;buf);\r\n    if (ret &lt; 0) {\r\n        CAMHAL_LOGEA(&quot;GetFrame: VIDIOC_DQBUF Failed&quot;);\r\n        return NULL;\r\n    }\r\n    nDequeued++;\r\n\r\n    index = mVideoInfo-&gt;buf.index;\r\n\r\n    return (char *)mVideoInfo-&gt;mem&#x5B;mVideoInfo-&gt;buf.index];\r\n}\r\n\r\n\r\n\r\nclass CameraFrame\r\n{\r\n    public:\r\n\r\n    enum FrameType\r\n        {\r\n            PREVIEW_FRAME_SYNC = 0x1, \/\/\/SYNC implies that the frame needs to be explicitly returned after consuming in order to be filled by camera again\r\n            PREVIEW_FRAME = 0x2   , \/\/\/Preview frame includes viewfinder and snapshot frames\r\n            IMAGE_FRAME_SYNC = 0x4, \/\/\/Image Frame is the image capture output frame\r\n            IMAGE_FRAME = 0x8,\r\n            VIDEO_FRAME_SYNC = 0x10, \/\/\/Timestamp will be updated for these frames\r\n            VIDEO_FRAME = 0x20,\r\n            FRAME_DATA_SYNC = 0x40, \/\/\/Any extra data assosicated with the frame. Always synced with the frame\r\n            FRAME_DATA= 0x80,\r\n            RAW_FRAME = 0x100,\r\n            SNAPSHOT_FRAME = 0x200,\r\n            ALL_FRAMES = 0xFFFF   \/\/\/Maximum of 16 frame types supported\r\n        };\r\n\r\n    enum FrameQuirks\r\n    {\r\n        ENCODE_RAW_YUV422I_TO_JPEG = 0x1 &lt;&lt; 0,\r\n        HAS_EXIF_DATA = 0x1 &lt;&lt; 1,\r\n    };\r\n\r\n    \/\/default contrustor\r\n    CameraFrame():\r\n        ...\r\n        {\r\n        ...\r\n    }\r\n\r\n    \/\/copy constructor\r\n    CameraFrame(const CameraFrame &amp;frame) :\r\n        ...\r\n        {\r\n        ...\r\n    }\r\n\r\n    void *mCookie;\r\n    void *mCookie2;\r\n    void *mBuffer;\r\n    int mFrameType;\r\n    nsecs_t mTimestamp;\r\n    unsigned int mWidth, mHeight;\r\n    uint32_t mOffset;\r\n    unsigned int mAlignment;\r\n    int mFd;\r\n    size_t mLength;\r\n    unsigned mFrameMask;\r\n    unsigned int mQuirks;\r\n    unsigned int mYuv&amp;#91;2&amp;#93;;\r\n    \/\/\/@todo add other member vars like  stride etc\r\n};&amp;#91;\/code&amp;#93;\r\n\u5bf9\u4e8eANativeWindowDisplayAdapter\uff0c\u5b83\u6709\u7ed9\u81ea\u5df1\u914d\u7f6e\u4e00\u4e2aFrameProvider\uff0c\u5b9e\u9645\u5c31\u662f\u4f1a\u53bb\u6ce8\u518c\u4e00\u79cdcallback\uff0c\u5f53\u4ecedriver\u56de\u6765\u6570\u636e\u7684\u65f6\u5019\u4f1a\u53bb\u901a\u77e5\u5bf9\u5e94\u7684callback\uff0c\u5f53\u7136\u8fd9\u4e2a\u65f6\u5019\u8fd8\u6ca1\u6709\u771f\u6b63\u7684\u53bb\u6ce8\u518c\r\n&amp;#91;code lang=&quot;java&quot;&amp;#93;int ANativeWindowDisplayAdapter::setFrameProvider(FrameNotifier *frameProvider)\r\n{\r\n    LOG_FUNCTION_NAME;\r\n\r\n    \/\/ Check for NULL pointer\r\n    if ( !frameProvider ) {\r\n        CAMHAL_LOGEA(&quot;NULL passed for frame provider&quot;);\r\n        LOG_FUNCTION_NAME_EXIT;\r\n        return BAD_VALUE;\r\n    }\r\n\r\n    \/\/Release any previous frame providers\r\n    if ( NULL != mFrameProvider ) {\r\n        delete mFrameProvider;\r\n    }\r\n\r\n    \/** Dont do anything here, Just save the pointer for use when display is\r\n         actually enabled or disabled\r\n    *\/\r\n    mFrameProvider = new FrameProvider(frameProvider, this, frameCallbackRelay);\r\n\r\n    LOG_FUNCTION_NAME_EXIT;\r\n\r\n    return NO_ERROR;\r\n}&amp;#91;\/code&amp;#93;\r\n\u5e76\u4e14\u6211\u4eec\u5728\u5f00\u59cbstart preview\u7684\u65f6\u5019\u4f1a\u53bb\u6ce8\u518c\u8fd9\u4e2acallback\uff0c\u5f53\u7136\u540c\u65f6\u5728start preview\u7684\u65f6\u5019\u6211\u4eec\u4e5f\u4f1a\u53bb\u5206\u914d\u4e00\u5757buffer\uff0c\u8fd9\u6837driver\u56de\u6765\u7684\u6570\u636e\u624d\u6709\u5730\u65b9\u653e\uff0c\u7533\u8bf7buffer\u5982\u4e0b\r\nCameraHal::allocateBuffer(...) __dequeue_buffer\r\n\r\n\u4e0b\u9762\u662f\u542f\u52a8living preview\uff0c\u6ce8\u518ccallback\r\n&amp;#91;code lang=&quot;java&quot;&amp;#93;int ANativeWindowDisplayAdapter::enableDisplay(int width, int height, struct timeval *refTime, S3DParameters *s3dParams)\r\n{\r\n    Semaphore sem;\r\n    TIUTILS::Message msg;\r\n\r\n    LOG_FUNCTION_NAME;\r\n\r\n    if ( mDisplayEnabled )\r\n        {\r\n        CAMHAL_LOGDA(&quot;Display is already enabled&quot;);\r\n        LOG_FUNCTION_NAME_EXIT;\r\n\r\n        return NO_ERROR;\r\n    }\r\n\r\n#if 0 \/\/TODO: s3d is not part of bringup...will reenable\r\n    if (s3dParams)\r\n        mOverlay-&gt;set_s3d_params(s3dParams-&gt;mode, s3dParams-&gt;framePacking,\r\n                                    s3dParams-&gt;order, s3dParams-&gt;subSampling);\r\n#endif\r\n\r\n#if PPM_INSTRUMENTATION || PPM_INSTRUMENTATION_ABS\r\n\r\n    if ( NULL != refTime )\r\n        {\r\n        Mutex::Autolock lock(mLock);\r\n        memcpy(&amp;mStandbyToShot, refTime, sizeof(struct timeval));\r\n        mMeasureStandby = true;\r\n    }\r\n\r\n#endif\r\n\r\n    \/\/Send START_DISPLAY COMMAND to display thread. Display thread will start and then wait for a message\r\n    sem.Create();\r\n    msg.command = DisplayThread::DISPLAY_START;\r\n\r\n    \/\/ Send the semaphore to signal once the command is completed\r\n    msg.arg1 = &amp;sem;\r\n\r\n    \/\/\/Post the message to display thread\r\n    mDisplayThread-&gt;msgQ().put(&amp;msg);\r\n\r\n    \/\/\/Wait for the ACK - implies that the thread is now started and waiting for frames\r\n    sem.Wait();\r\n\r\n    \/\/ Register with the frame provider for frames\r\n    mFrameProvider-&gt;enableFrameNotification(CameraFrame::PREVIEW_FRAME_SYNC);\r\n\r\n    mDisplayEnabled = true;\r\n    mPreviewWidth = width;\r\n    mPreviewHeight = height;\r\n\r\n    CAMHAL_LOGVB(&quot;mPreviewWidth = %d mPreviewHeight = %d&quot;, mPreviewWidth, mPreviewHeight);\r\n\r\n    LOG_FUNCTION_NAME_EXIT;\r\n\r\n    return NO_ERROR;\r\n}\r\n\r\n\r\n\r\nint FrameProvider::enableFrameNotification(int32_t frameTypes)\r\n{\r\n    LOG_FUNCTION_NAME;\r\n    status_t ret = NO_ERROR;\r\n\r\n    \/\/\/Enable the frame notification to CameraAdapter (which implements FrameNotifier interface)\r\n    mFrameNotifier-&gt;enableMsgType(frameTypes&lt;&lt;MessageNotifier::FRAME_BIT_FIELD_POSITION\r\n                                    , mFrameCallback\r\n                                    , NULL\r\n                                    , mCookie\r\n                                    );\r\n\r\n    \/\/ \u628aframeCallbackRelay\u8fd9\u4e2acallback\u6dfb\u52a0\u5230BaseCameraAdapter::mFrameSubscribers\u5f53\u4e2d\r\n    \/\/ \u8be6\u89c1 preview_stream_ops_t*  mANativeWindow; \u4ee5\u53ca ANativeWindowDisplayAdapter::PostFrame(...)\r\n    \/\/ __enqueue_buffer\r\n    \/\/ \u6700\u7ec8\u4f1a\u628a\u6570\u636e\u66f4\u65b0\u5230CameraHardwareInterface\u7684sp&lt;ANativeWindow&gt; mPreviewWindow;\r\n    \/\/ \u8fd9\u6837\u6bcf\u8fc7\u6765\u4e00\u4e2aframe\uff0c\u4e5f\u5c31\u662f\u53bb\u66f4\u65b0\u6211\u4eec\u770b\u5230\u7684preview\u6570\u636e\r\n\r\n    LOG_FUNCTION_NAME_EXIT;\r\n    return ret;\r\n}<\/pre>\n<p>\u89c1\u4e0a\u9762\u4ee3\u7801\u4e2d\u5d4c\u7684\u4e2d\u6587\u6ce8\u91ca\uff0c\u8fd9\u662f\u6211\u52a0\u7684\u3002<\/p>\n<p>\u53e6\u5916displayThread(\u4f4d\u4e8eANativeWindowDisplayAdapter\u5f53\u4e2d)\uff0cdisplayThread\u4f1a\u542c\u4e00\u4e9b\u4eceCamera HAL\u6765\u7684\u6d88\u606f\uff0c\u7136\u540e\u6267\u884c\uff0c\u8fd9\u4e2a\u4e0d\u662f\u5f88\u590d\u6742\u3002<\/p>\n<p>\u4e5f\u5c31\u662f\u8bf4\u6570\u636e\u662f\u600e\u4e48\u8d34\u5230view\u4e0a\u53bb\uff0c\u8fd9\u662fHAL\u8fd9\u5c42\u505a\u6389\u7684\uff0c\u867d\u7136\u53ea\u662f\u4e00\u4e2a\u8c03\u7528ANativeWindow\u7684\u8fc7\u7a0b\uff01<\/p>\n<p>\u8fd9\u53ea\u662f\u4e00\u4e2a\u7c97\u7cd9\u7684\u8fc7\u7a0b\uff0c\u518d\u6df1\u5c42\u7ec6\u8282\u7684\u6211\u4eec\u6682\u65f6\u4e0d\u7814\u7a76\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u60f3\u8981\u77e5\u9053Driver\u56de\u6765\u7684\u6570\u636e\u662f\u5982\u4f55\u663e\u793a\u51fa\u6765\u7684\u5417\uff1f \u8fd9\u4e2a\u5de5\u4f5c\u662f\u5728\u54ea\u4e00\u5c42\u505a\u7684\uff1f \u8ddf\u8e2a\u4ee3\u7801\u5c31\u53ef\u4ee5\u7406\u6e05\u695a\u8fd9\u4e00\u8fc7\u7a0b\uff0c\u8fd9\u91cc\u4ee3\u7801\u7248\u672c\u662fJelly Bean\u3002 CameraHardwareInterface\u521d\u59cb\u5316\u7684\u65f6\u5019\u4f1a\u521d\u59cb\u5316\u597dwindow\u6709\u5173\u7684\u6570\u636e struct camera_preview_window { struct preview_stream_ops nw; void *user; }; struct camera_preview_window mHalPreviewWindow; status_t initialize(hw_module_t *module) { ALOGI(&quot;Opening camera %s&quot;, mName.string()); int rc = module-&gt;methods-&gt;open(module, mName.string(), (hw_device_t **)&amp;mDevice); if (rc != OK) { ALOGE(&quot;Could not open camera %s: %d&quot;, mName.string(), rc); return rc; } initHalPreviewWindow(); return rc; } void initHalPreviewWindow() { &hellip; <a href=\"https:\/\/guoh.ai\/lifelog\/2013\/07\/the-process-of-driver-data-shown-on-viewfinder\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Driver\u56de\u6765\u7684\u6570\u636e\u662f\u5982\u4f55\u663e\u793a\u51fa\u6765\u7684<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_gfmr_meta_descriptions":[],"_gfmr_multilingual_taxonomy_terms":[],"footnotes":""},"categories":[7,28,34],"tags":[27],"class_list":["post-647","post","type-post","status-publish","format-standard","hentry","category-android","category-cpp","category-multimedia","tag-camera"],"_links":{"self":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/647","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/comments?post=647"}],"version-history":[{"count":3,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/647\/revisions"}],"predecessor-version":[{"id":649,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/posts\/647\/revisions\/649"}],"wp:attachment":[{"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/media?parent=647"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/categories?post=647"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/guoh.ai\/lifelog\/wp-json\/wp\/v2\/tags?post=647"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}