0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

全志R128芯片基礎(chǔ)組件開(kāi)發(fā)指南—RTOS多媒體編碼

冬至子 ? 來(lái)源:丨budboool ? 作者:丨budboool ? 2023-10-11 14:54 ? 次閱讀

RTOS 多媒體編碼

介紹 FreeRTOS 下如何使用 xrecorder 的接口來(lái)開(kāi)發(fā)錄制應(yīng)用程序,方便錄制應(yīng)用開(kāi)發(fā)人員快速正確地開(kāi)發(fā),以及錄制應(yīng)用測(cè)試人員如何根據(jù)該文檔對(duì)基于 xrecord 的錄制應(yīng)用進(jìn)行驗(yàn)證測(cè)試。

編碼支持情況

目前 RTOS 平臺(tái)多媒體編碼應(yīng)用支持的編碼格式分別為:pcm、amr、mp3、speex、opus。

其中 pcm、amr、mp3 可通過(guò) xrecorder 進(jìn)行編碼以及錄制;speex 和 opus 可通過(guò)第三方示例工程進(jìn)行編碼。

xrecorder 狀態(tài)圖

在這里插入圖片描述

這張狀態(tài)轉(zhuǎn)換圖清晰地描述了 xrecorder 的各個(gè)狀態(tài),也列舉了主要的方法的調(diào)用時(shí)序,每種方法只能在一些特定的狀態(tài)下使用,否則會(huì)出錯(cuò)。

Init 狀態(tài)

Idle 狀態(tài):當(dāng)調(diào)用 XRecordCreate() 創(chuàng)建一個(gè) xrecord 時(shí),處于 idle 狀態(tài)。

Prepared 狀態(tài)

調(diào)用 XRecordPrepare() 函數(shù)并返回后,xrecorder 處于 Prepared 狀態(tài)。在這個(gè)狀態(tài)下說(shuō)明所有的資源都已經(jīng)就緒了,調(diào)用 XRecordStart() 函數(shù)就可以進(jìn)行錄制。

Started 狀態(tài)

xrecorder prepare 完成后,調(diào)用 XRecordStart() 進(jìn)行錄制,當(dāng)應(yīng)用開(kāi)始錄制后,xrecorder 就處于 Started 狀態(tài),這表明 xrecorder 正在錄制文件。

Stopped 狀態(tài)

Started 狀態(tài)下均可調(diào)用 XrecordStop() 停止 xrecorder,而處于 Stop 狀態(tài)的 xrecorder 要想重新錄制,需要通過(guò) XRecorderPrepare() 回到先前的 Prepared 狀態(tài)重新開(kāi)始才可以。

Destroyed 狀態(tài)

通過(guò) XRecordDestroy() 的方法可以進(jìn)入 Destroyed 狀態(tài),只要 xrecorder 不再被使用,就應(yīng)當(dāng)盡快將其 destroy 掉。

接口函數(shù)

創(chuàng)建一個(gè) XRecord

XRecord *XRecordCreate()

參數(shù)

  • 無(wú)

返回值:

  • 無(wú)

設(shè)置錄制音頻的編碼格式

int XRecordSetAudioEncodeType(XRecord *p, XRECODER_AUDIO_ENCODE_TYPE type, XRecordConfig *config)

參數(shù):

  • p: 通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針
  • type: 已支持的編碼格式
  • config: 上層應(yīng)用對(duì)音頻屬性的配置

返回值:

  • 成功: 0; 失敗: ?1

獲取指針

獲取指向音頻設(shè)備管理模塊的指針,用于錄制音頻

void XRecordSetAudioCap(XRecord* p, const CaptureCtrl* audioSrc)

參數(shù):

  • p: 通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針
  • audioSrc: 由上層應(yīng)用獲取的音頻設(shè)備管理模塊的指針

返回值:

  • 無(wú)

audioSrc 可在上層應(yīng)用通過(guò)調(diào)用 cedarx 的音頻設(shè)備管理模塊的 RTCaptureDeviceCreate 來(lái)創(chuàng)建。

設(shè)置錄制后文件的保存的路徑

int XRecordSetDataDstUrl(XRecord* p, const char* pUrl, void* arg, const CdxKeyedVectorT* pHeaders)

參數(shù):

  • p: 通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針
  • pUrl:url 地址

返回值:

  • 成功:0;失?。?1

將 XRecord 置為準(zhǔn)備狀態(tài), 準(zhǔn)備 Muxer

int XRecordPrepare(XRecord* p)

參數(shù):

  • p:通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針

返回值:

  • 成功:0;失?。?1

將 XRecord 置為啟動(dòng)狀態(tài)

int XRecordStart(XRecord* p)

參數(shù):

  • p:通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針

返回值:

  • 成功:0;失?。?1

將 XRecord 置為停止?fàn)顟B(tài)

int XRecordStop(XRecord* p)

參數(shù):

  • p:通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針

返回值:

  • 成功: 0;失?。?1

編碼數(shù)據(jù)入隊(duì)封裝

提供接口給下層編碼模塊,將編碼數(shù)據(jù)放進(jìn)緩存隊(duì)列中等待封裝

int onAudioDataEnc(XRecord* app, CdxMuxerPacketT* buff)

參數(shù):

  • app: xrecorder 的環(huán)境句柄;
  • buff:編碼后的緩存數(shù)據(jù)

返回值:

  • 成功: 0;失?。?1

銷(xiāo)毀一個(gè) XRecord

int XRecordDestroy(XRecord* p)

參數(shù):

  • p:通過(guò) XRecordCreate 創(chuàng)建的 XRecord 指針

返回值:

  • 成功: 0;失?。?1

XRecorder 開(kāi)發(fā)流程

  1. XRecordCreate() //創(chuàng)建一個(gè)錄制應(yīng)用
  2. XRecordSetAudioCap() //設(shè)置音頻采集設(shè)備;可先調(diào)用 RTCaptureDeviceCreate 創(chuàng)建。
  3. XRecordSetDataDstUrl() //設(shè)置錄制后文件保存位置
  4. XRecordSetAudioEncodeType() //設(shè)置音頻數(shù)據(jù)的編碼格式
  5. XRecordPrepare() //設(shè)置 Muxer,讓 xrecorder 進(jìn)入準(zhǔn)備狀態(tài)
  6. XRecordStart() //開(kāi)始錄制
  7. XRecordStop() //停止錄制
  8. XRecordDestroy() //當(dāng)不需要進(jìn)行錄制的時(shí)候,銷(xiāo)毀 xrecorder

注意事項(xiàng)

  1. 在調(diào)用 XRecordSetAudioCap 設(shè)置音頻采集設(shè)備之前,需先打開(kāi)音頻采集設(shè)備來(lái)獲取句柄。在 rtos 平臺(tái)可調(diào)用 libcedarx 提供的音頻采集設(shè)備控制模塊 rtosCaptureControl.c 中的 RTCaptureDeviceCreate 來(lái)創(chuàng)建句柄。
  2. recorder 應(yīng)用未支持暫停錄制。
  3. recorder 的錄制時(shí)長(zhǎng)為調(diào)用 XRecordStart 至調(diào)用 XRecordStop 之間的時(shí)長(zhǎng)來(lái)決定,因此上層應(yīng)用需要錄制指定時(shí)長(zhǎng)的音頻時(shí),錄制的步驟應(yīng)為調(diào)用 XRecordStart,等待指定的時(shí)間,調(diào)用XRecordStop。

示例代碼

#include < stdio.h >
#include < stdlib.h >
#include < stdbool.h >
#include < string.h >
#include < aw_common.h >
#include < console.h >

#include "vfs.h"
#include "xrecord.h"

#define RECORDER_LOGD(msg, arg...)      printf("[RECORDER_DBG] < %s : %d > " msg "n", __func__, __LINE__, ##arg)
#define RECORDER_LOGI(msg, arg...)      printf("[RECORDER_INFO] < %s : %d > " msg "n", __func__, __LINE__, ##arg)
#define RECORDER_LOGW(msg, arg...)      printf("[RECORDER_WRN] < %s : %d > " msg "n", __func__, __LINE__, ##arg)
#define RECORDER_LOGE(msg, arg...)      printf("[RECORDER_ERR] < %s : %d > " msg "n", __func__, __LINE__, ##arg)

typedef struct recorder_base recorder_base;

typedef struct rec_cfg
{
    XRECODER_AUDIO_ENCODE_TYPE   type;
    int                 sample_rate;
    int                 chan_num;
    int                 bitrate;
    int                 sampler_bits;
} rec_cfg;

struct recorder_base
{
    int (*start)(recorder_base *base, const char *url, const rec_cfg *cfg);
    int (*stop)(recorder_base *base);
};

struct ExampleCustomerWriterImpl
{
    CdxWriterT base;
    vfs_file_t *vfs;
};

typedef struct recorder
{
    recorder_base base;
    XRecord *xrecorder;
    CaptureCtrl *cap;
} recorder;

recorder_base *recorder_create();
int recorder_destroy(recorder_base *base);

/* Example Customer Writer */
static int __CdxExampleConnect(CdxWriterT *writer)
{
    struct ExampleCustomerWriterImpl *impl;

    impl = (struct ExampleCustomerWriterImpl *)writer;

    vfs_unlink("data/record/2.amr");
    impl- >vfs = vfs_open("data/record/2.amr", VFS_RDWR | VFS_CREAT);
    if (impl- >vfs == NULL) {
        return -1;
    }

    return 0;
}

static int __CdxExampleRead(CdxWriterT *writer, void *buf, int size)
{
    return 0;
}

static int __CdxExampleWrite(CdxWriterT *writer, void *buf, int size)
{
    uint32_t write_len;
    struct ExampleCustomerWriterImpl *impl;

    impl = (struct ExampleCustomerWriterImpl *)writer;

    write_len = vfs_write(impl- >vfs, buf, size);

    return write_len;
}

static long __CdxExampleSeek(CdxWriterT *writer, long moffset, int mwhere)
{
    return 0;
}

static long __CdxExampleTell(CdxWriterT *writer)
{
    return 0;
}

static int __CdxExampleClose(CdxWriterT *writer)
{
    struct ExampleCustomerWriterImpl *impl;

    impl = (struct ExampleCustomerWriterImpl *)writer;

    vfs_close(impl- >vfs);
    free(impl);

    return 0;
}

static const struct CdxWriterOps exampleCustomerWriteOps =
{
    .cdxConnect   =  __CdxExampleConnect,
    .cdxRead      =  __CdxExampleRead,
    .cdxWrite     =  __CdxExampleWrite,
    .cdxSeek      =  __CdxExampleSeek,
    .cdxTell      =  __CdxExampleTell,
    .cdxClose     =  __CdxExampleClose
};

CdxWriterT *ExampleCustomerWriterCreat()
{
    struct ExampleCustomerWriterImpl *impl;

    impl = malloc(sizeof(*impl));
    if (impl == NULL) {
        printf("example customer writer create fail.n");
        return NULL;
    }

    memset(impl, 0, sizeof(*impl));

    impl- >base.ops = &exampleCustomerWriteOps;

    return &impl- >base;
}

/* Main App */
static void showHelp(){
    printf("n");
    printf("**************************n");
    printf("* This is a simple audio recoder, when it is started, you can input commands to telln");
    printf("* what you want it to do.n");
    printf("* Usage: n");
    printf("*   cedarx_record amr 10  : this means record 10s amr musicn");
    printf("*   cedarx_record pcm 10  : this means record 10s pcm musicn");
    printf("**************************n");
}

recorder *recorder_singleton = NULL;

static int record_start(recorder_base *base, const char *url, const rec_cfg *cfg)
{
    recorder *impl = container_of(base, recorder, base);

    XRecordConfig audioConfig;

    if (cfg- >type == XRECODER_AUDIO_ENCODE_PCM_TYPE)
    {
        audioConfig.nChan = cfg- >chan_num;
        audioConfig.nSamplerate = cfg- >sample_rate;
        audioConfig.nSamplerBits = cfg- >sampler_bits;
        audioConfig.nBitrate = cfg- >bitrate;
    }
    else if (cfg- >type == XRECODER_AUDIO_ENCODE_AMR_TYPE)
    {
        audioConfig.nChan = 1;
        audioConfig.nSamplerate = 8000;//amr-nb 8000Hz amr-wb 16000Hz
        audioConfig.nSamplerBits = 16;
        audioConfig.nBitrate = 12200;//amr-nb 12200  amr-wb 23850
    } else {
        audioConfig.nChan = cfg- >chan_num;
        audioConfig.nSamplerate = cfg- >sample_rate;
        audioConfig.nSamplerBits = cfg- >sampler_bits;
        audioConfig.nBitrate = cfg- >bitrate;
    }

    XRecordSetDataDstUrl(impl- >xrecorder, url, NULL, NULL);
    XRecordSetAudioEncodeType(impl- >xrecorder, cfg- >type, &audioConfig);

    XRecordPrepare(impl- >xrecorder);
    XRecordStart(impl- >xrecorder);
    RECORDER_LOGI("record start");
    return 0;
}

static int record_stop(recorder_base *base)
{
    recorder *impl = container_of(base, recorder, base);
    XRecordStop(impl- >xrecorder);
    return 0;
}

extern CaptureCtrl* RTCaptureDeviceCreate();

recorder_base *recorder_create()
{
    if (recorder_singleton != NULL)
        return &recorder_singleton- >base;

    recorder *impl = malloc(sizeof(*impl));
    if (impl == NULL)
        return NULL;
    memset(impl, 0, sizeof(*impl));

    impl- >xrecorder = XRecordCreate();
    if (impl- >xrecorder == NULL)
        goto failed;

    impl- >cap = (void *)(uintptr_t)RTCaptureDeviceCreate();
    if (impl- >cap == NULL)
        goto failed;
    XRecordSetAudioCap(impl- >xrecorder, impl- >cap);

    impl- >base.start = record_start;
    impl- >base.stop = record_stop;

    recorder_singleton = impl;

    return &impl- >base;

failed:
    RECORDER_LOGE("recorder create failed");
    if (impl- >xrecorder)
        XRecordDestroy(impl- >xrecorder);
    if (impl)
        free(impl);
    return NULL;
}

int recorder_destroy(recorder_base *base)
{
    recorder *impl = container_of(base, recorder, base);

    if (impl- >xrecorder) {
        XRecordDestroy(impl- >xrecorder);
    }

    free(impl);

    recorder_singleton = NULL;

    return 0;
}

static int cedarx_record_test(int argc, char **argv)
{
    recorder_base *recorder;
    rec_cfg cfg;
    char music_url[64];
    char file_url[64];
    CdxWriterT *writer;
    memset(file_url, 0, 64);
    if(argc == 3){
        if( !strncmp("amr", argv[1], sizeof("amr")-1) ){
            cfg.type = XRECODER_AUDIO_ENCODE_AMR_TYPE;
            snprintf(file_url, 64, "file://data/%ds.amr", atoi(argv[2]));
            cfg.sample_rate = 8000;//8000
            cfg.chan_num = 1;//1
            cfg.bitrate = 12200;
            cfg.sampler_bits = 16;
        }
        else if( !strncmp("pcm", argv[1], sizeof("pcm")-1) ){
            cfg.type = XRECODER_AUDIO_ENCODE_PCM_TYPE;
            snprintf(file_url, 64, "file://data/%ds.pcm", atoi(argv[2]));
            cfg.sample_rate = 8000;//8000
            cfg.chan_num = 1;//1
            cfg.bitrate = 12200;
            cfg.sampler_bits = 16;
        }
        else if( !strncmp("mp3", argv[1], sizeof("mp3")-1) ){
            cfg.type = XRECODER_AUDIO_ENCODE_MP3_TYPE;
            snprintf(file_url, 64, "file://data/%ds.mp3", atoi(argv[2]));
            cfg.sample_rate = 16000;
            cfg.chan_num = 1;
            cfg.bitrate = 32000;
            cfg.sampler_bits = 16;
        } else {
            printf("now support!n");
            return -1;
        }
    }else{
        printf("the parameter is error,usage is as following:n");
        showHelp();
        return -1;
    }

    recorder = recorder_create();
    if (recorder == NULL) {
        printf("recorder create fail, exitn");
        return -1;
    }

    printf("===start record %s now, last for %d s===n", argv[1], atoi(argv[2]));
    recorder- >start(recorder, file_url, &cfg);
    sleep(atoi(argv[2]));
    recorder- >stop(recorder);
    printf("record %s over.n", argv[1]);
exit:
    return recorder_destroy(recorder);
}

FINSH_FUNCTION_EXPORT_CMD(cedarx_record_test, cedarx_record, cedarx record test demo);
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • FreeRTOS
    +關(guān)注

    關(guān)注

    12

    文章

    483

    瀏覽量

    61724
  • 音頻編碼器
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    9101
  • 緩存器
    +關(guān)注

    關(guān)注

    0

    文章

    63

    瀏覽量

    11624
  • vfs
    vfs
    +關(guān)注

    關(guān)注

    0

    文章

    13

    瀏覽量

    5235
  • R128
    +關(guān)注

    關(guān)注

    0

    文章

    41

    瀏覽量

    76
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    R128基礎(chǔ)組件開(kāi)發(fā)指南RTOS多媒體解碼

    介紹 FreeRTOS 下如何使用 rtplayer 的接口來(lái)開(kāi)發(fā)播放器應(yīng)用程序,方便播放器開(kāi)發(fā)人員快速正確地開(kāi)發(fā),以及播放器測(cè)試人員如何根據(jù)該文檔對(duì) rtplayer 播放器進(jìn)行驗(yàn)證測(cè)試。
    的頭像 發(fā)表于 10-10 16:28 ?2059次閱讀
    <b class='flag-5'>全</b><b class='flag-5'>志</b><b class='flag-5'>R128</b>基礎(chǔ)<b class='flag-5'>組件</b><b class='flag-5'>開(kāi)發(fā)指南</b>—<b class='flag-5'>RTOS</b><b class='flag-5'>多媒體</b>解碼

    R128 BLE最高吞吐量測(cè)試正確配置測(cè)試方法

    R128使用前我們需要了解BLE的最高吞吐量,以方便評(píng)估相關(guān)功能的開(kāi)發(fā)。
    的頭像 發(fā)表于 10-27 17:17 ?1119次閱讀
    <b class='flag-5'>全</b><b class='flag-5'>志</b><b class='flag-5'>R128</b> BLE最高吞吐量測(cè)試正確配置測(cè)試方法

    R128內(nèi)存泄漏調(diào)試案例分享

    硬件:R128 軟件:FreeRTOS + rtplayer\_test(Cedarx)+ AudioSystem
    的頭像 發(fā)表于 11-20 17:27 ?836次閱讀
    <b class='flag-5'>全</b><b class='flag-5'>志</b><b class='flag-5'>R128</b>內(nèi)存泄漏調(diào)試案例分享

    詳解R128 RTOS安全方案功能

    介紹 R128 下安全方案的功能。安全完整的方案基于標(biāo)準(zhǔn)方案擴(kuò)展,覆蓋硬件安全、硬件加解密引擎、安全啟動(dòng)、安全系統(tǒng)、安全存儲(chǔ)等方面。 配置文件相關(guān)本文涉及到一些配置文件,在此進(jìn)行說(shuō)明。 env
    發(fā)表于 12-28 15:59

    R128入門(mén)編寫(xiě)HelloWorld

    本文將介紹使用 R128 開(kāi)發(fā)板從串口輸出 HelloWorld 的方式介紹 SDK 軟件開(kāi)發(fā)流程。 載入方案我們使用的開(kāi)發(fā)板是 R128-
    發(fā)表于 12-29 09:39

    R128硬件設(shè)計(jì)指南

    (ANT pin)無(wú)需匹配電路,但可預(yù)留天線(xiàn) PI 型匹配電路。如上圖所示。為了方便天線(xiàn)PI型匹配電路調(diào)試,需在射頻輸出端口與天線(xiàn)間預(yù)留 0Ω電阻 WR1。如圖所示。 因 R128 芯片射頻前端已
    發(fā)表于 01-04 09:23

    R128 SDK架構(gòu)與目錄結(jié)構(gòu)

    R128 S2 是提供的一款 M33(ARM)+C906(RISCV-64)+HIFI5(Xtensa) 三核異構(gòu) SoC,同時(shí)芯片內(nèi)部 SIP 有 1M SRAM、8M LSPS
    發(fā)表于 01-05 10:05

    R128 Devkit開(kāi)發(fā)板原理圖模塊介紹及使用說(shuō)明

    :CH341SER.EXE 購(gòu)買(mǎi)鏈接 百問(wèn)科技淘寶店 - R128 DevKit 原理圖模塊介紹R128 模組R128 模組使用 SMT
    發(fā)表于 01-17 09:45

    R128芯片 基礎(chǔ)組件開(kāi)發(fā)指南——RTOS 多媒體解碼

    RTOS 多媒體解碼 介紹 FreeRTOS 下如何使用 rtplayer 的接口來(lái)開(kāi)發(fā)播放器應(yīng)用程序,方便播放器開(kāi)發(fā)人員快速正確地開(kāi)發(fā),以
    發(fā)表于 10-10 13:52

    R128芯片應(yīng)用開(kāi)發(fā)案例——按鍵輸入

    按鍵3腳 載入方案 我們使用的開(kāi)發(fā)板是 R128-Devkit,需要開(kāi)發(fā) C906 核心的應(yīng)用程序,所以載入方案選擇r128s2_module_c906 $ source envse
    發(fā)表于 10-10 14:34

    R128芯片 基礎(chǔ)組件開(kāi)發(fā)指南——RTOS 多媒體編碼

    RTOS 多媒體編碼 介紹 FreeRTOS 下如何使用 xrecorder 的接口來(lái)開(kāi)發(fā)錄制應(yīng)用程序,方便錄制應(yīng)用開(kāi)發(fā)人員快速正確地
    發(fā)表于 10-11 09:52

    R128芯片 基礎(chǔ)組件開(kāi)發(fā)指南——RTOS 多媒體編碼

    RTOS 多媒體編碼 介紹 FreeRTOS 下如何使用 xrecorder 的接口來(lái)開(kāi)發(fā)錄制應(yīng)用程序,方便錄制應(yīng)用開(kāi)發(fā)人員快速正確地
    發(fā)表于 10-12 15:49

    R128軟件配置——RTOS 軟件包配置

    說(shuō)明 了解了menuconfig的基本操作,我們?cè)賮?lái)了解一下 RTOS 的 menuconfig 具體都有是么內(nèi)容。 Build target sunxi arch 分別選擇芯片的系列,對(duì)于R128
    發(fā)表于 10-20 15:31

    DshanMCU-R128s2 R128 EVT 開(kāi)發(fā)套件

    針對(duì) R128 模組,百問(wèn)科技推出了 R128 EVT 開(kāi)發(fā)套件作為快速開(kāi)發(fā)評(píng)估工具。
    的頭像 發(fā)表于 12-22 15:16 ?608次閱讀
    DshanMCU-<b class='flag-5'>R128</b>s2 <b class='flag-5'>R128</b> EVT <b class='flag-5'>開(kāi)發(fā)</b>套件

    DshanMCU-R128s2 SDK 架構(gòu)與目錄結(jié)構(gòu)

    R128 S2 是提供的一款 M33(ARM)+C906(RISCV-64)+HIFI5(Xtensa) 三核異構(gòu) SoC,同時(shí)芯片內(nèi)部 SIP 有 1M SRAM、8M LSPS
    的頭像 發(fā)表于 12-22 15:57 ?558次閱讀
    DshanMCU-<b class='flag-5'>R128</b>s2 SDK 架構(gòu)與目錄結(jié)構(gòu)