NDK Android平台openSLES音频采集和播放

2023-10-30

《Android平台使用openSLES采集麦克风音频代码实现》链接:

https://edu.csdn.net/learn/38258/606150?spm=1003.2001.3001.4157

《Android平台使用openSLES播放PCM音频代码实现》链接:

https://edu.csdn.net/learn/38258/606151?spm=1003.2001.3001.4157

一、前言

      在音视频技术中音频的采集和播放是一个及其重要的过程,学习了解音频的采集和播放过程有助于我们对音频技术有更近一步的了解。在前面的文章中我们已经介绍过windows平台下的音频采集和播放,本篇文章讲介绍在Android平台下的音频采集和播放。

二、openSLES介绍

       OpenSL ES(Open Sound Lib Embedded system)嵌入式系统开放声音库,是一种免版税、跨平台、硬件加速的 C 语言音频API。OpenSL ES能夸平台使用方便音频功能的移植。Android平台的OpenSL ES继承了OpenSL ES参考规范里面的大部分功能,但仍有部分差异所以使用OpenSL ES参考规范的参考代码可能需要修改才能使其在 Android 系统中正常工作。 Android平台里的OpenSL ES与 Android Java 框架中的 MediaPlayer 和 MediaRecorderAPI 提供类似的音频功能。在Android NDK中可以直接使用Android平台的OpenSL ES的API接口进行c/c++的音频项目开发。

      Android NDK使用OpenSL ES开发音频项目的文档以及demo连接如下:OpenSL ES  |  Android NDK  |  Android Developers

三、openSLES主要API介绍

     openSLES的开发过程需要了解两个概念:对象和接口。OpenSL ES 的对象类似于 Java 和 C++ 等编程语言中的对象概念,只不过OpenSL ES 的对象只能通过其关联接口进行访问,即要访问对象的每种功能需要获取接口来获取对应功能的接口。在音频采集或播放的过程,首先要创建对象,讲对象实例化(realize),通过对象的GetInterface获取对象的功能接口,通过接口来配置参数,使用完成后通过Destroy来销毁对象的接口。

        在android中使用OpenSL ES需要包含下面的头文件:

#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

      通过配置需要在AndroidManifest.xml的配置文件里面增加录音权限,配置如下:

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

     CMake编译的时候需要在CMakeList.txt中加入OpenSLES的链接,如下:

target_link_libraries(
    OpenSLES
)

四、openSLES采集音频代码示例

1、采集引擎初始化

static int createAudioCaptureEngine()
{
    SLEngineOption pEngineOptions[] = {(SLuint32) SL_ENGINEOPTION_THREADSAFE,
                                       (SLuint32) SL_BOOLEAN_TRUE};
    // 创建引擎对象,
    SLresult ret;
    ret = slCreateEngine(
            &m_audioCaptureMng.engineObject, //对象地址,
            1, //配置参数数量
            pEngineOptions, //配置参数,
            0,  
            NULL,
            NULL
    );

    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio slCreateEngine is error %d\n",ret);
        return -1;
    }
 
    //实例化这个对象
    ret = (*m_audioCaptureMng.engineObject)->Realize(m_audioCaptureMng.engineObject, SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Realize is error %d\n",ret);
        return -1;
    }
    
    //从这个对象里面获取引擎接口
    (*m_audioCaptureMng.engineObject)->GetInterface(m_audioCaptureMng.engineObject, SL_IID_ENGINE, &m_audioCaptureMng.engineInterface);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }

    return 0;
}

2、音频采集配置

static int initAudioCapture(int micId, int sample, int chn,int frameLen)
{
    int ckSample = checkSampleRate(sample);
    m_audioCaptureMng.micCfgInfo.curChn    = chn > 2 ? 2:(chn < 1 ? 1 : chn);
    m_audioCaptureMng.micCfgInfo.curFrameLenPerChn = frameLen > 2048 ? 2048 : (frameLen < 960 ? 960 : frameLen);
    int chnCfg = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;//双声道
    if ( m_audioCaptureMng.micCfgInfo.curChn == 1)//单声道
    {
        chnCfg = SL_SPEAKER_FRONT_CENTER;
    }
    SLDataLocator_IODevice ioDevice = 
    {
            SL_DATALOCATOR_IODEVICE,         //类型 IO设备类型,手机内置麦克
            SL_IODEVICE_AUDIOINPUT,          //设备类型  选择了音频输入类型
            SL_DEFAULTDEVICEID_AUDIOINPUT,   //设备ID
            NULL//device实例
    };

    // 输入,SLDataSource 表示音频数据来源的信息
    SLDataSource capSource = 
    {
            &ioDevice,//SLDataLocator_IODevice配置输入
            NULL//输入格式,采集的并不需要
    };

// 数据源简单缓冲队列定位器,输出buffer队列
    SLDataLocator_AndroidSimpleBufferQueue capBufferQueue = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //buff 类型 
            NUM_BUFFER_QUEUE //buffer的数量
    };
// PCM 数据源格式 //设置输出数据的格式
    SLDataFormat_PCM pcmCfg = {
            SL_DATAFORMAT_PCM, //输出PCM格式的数据
            (SLuint32)m_audioCaptureMng.micCfgInfo.curChn,  //  //输出的声道数量
            (SLuint32)ckSample, //采样频率
            SL_PCMSAMPLEFORMAT_FIXED_16, //输出的采样格式,这里是16bit
            SL_PCMSAMPLEFORMAT_FIXED_16,
            (SLuint32)chnCfg,//道配置,
            SL_BYTEORDER_LITTLEENDIAN //PCM数据的大小端排列
    };
// 输出,SLDataSink 表示音频数据输出信息
    SLDataSink dataSink = {
            &capBufferQueue, //SLDataFormat_PCM配置输出
            &pcmCfg //输出数据格式
    };

    //创建录制的对象,并且指定开放SL_IID_ANDROIDSIMPLEBUFFERQUEUE这个接口
    SLInterfaceID iids[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                             SL_IID_ANDROIDCONFIGURATION};
    SLboolean required[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    // 创建 audio recorder 对象
    int ret = (*m_audioCaptureMng.engineInterface)->CreateAudioRecorder(m_audioCaptureMng.engineInterface, //引擎接口
                                                  &m_audioCaptureMng.recorderObject, //录制对象地址,
                                                  &capSource,//输入配置
                                                  &dataSink,//输出配置
                                                  1,//支持的接口数量
                                                  iids, //具体的要支持的接口
                                                  required //具体的要支持的接口是开放的还是关闭的
    );
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio CreateAudioRecorder is error %d\n",ret);
        return -1;
    }

     //实例化这个录制对象
    ret = (*m_audioCaptureMng.recorderObject)->Realize(m_audioCaptureMng.recorderObject, SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Realize is error %d\n",ret);
        return -1;
    }
    //获取Buffer接口
    ret = (*m_audioCaptureMng.recorderObject)->GetInterface(m_audioCaptureMng.recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                                             (void *) &m_audioCaptureMng.recorderBufferQueue);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }

     //获取录制接口
    (*m_audioCaptureMng.recorderObject)->GetInterface(m_audioCaptureMng.recorderObject, SL_IID_RECORD, &m_audioCaptureMng.audioRecord);
     if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }
    // 注册回调接口,在回调接口中获取pcm数据保存到 buff队列中
    ret = (*m_audioCaptureMng.recorderBufferQueue)->RegisterCallback(m_audioCaptureMng.recorderBufferQueue, AudioRecordCallback,NULL);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio RegisterCallback is error %d\n",ret);
        return -1;
    }
    

    //设置录制器为录制状态 SL_RECORDSTATE_RECORDING
    ret = (*m_audioCaptureMng.audioRecord)->SetRecordState(m_audioCaptureMng.audioRecord, SL_RECORDSTATE_RECORDING);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio SetRecordState is error %d\n",ret);
        return -1;
    }
    m_audioCaptureMng.recorderSize = m_audioCaptureMng.micCfgInfo.curChn*m_audioCaptureMng.micCfgInfo.curFrameLenPerChn * sizeof(short);
    m_audioCaptureMng.recorderBuffer = new char [m_audioCaptureMng.recorderSize];
    //在设置完录制状态后一定需要先Enqueue一次,这样的话才会开始采集回调
    (*m_audioCaptureMng.recorderBufferQueue)->Enqueue(m_audioCaptureMng.recorderBufferQueue, m_audioCaptureMng.recorderBuffer, m_audioCaptureMng.recorderSize);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Enqueue is error %d\n",ret);
        return -1;
    }
    LOGI("init Audio recording succeed!(%d,%d,%d,recorderSize %d)~~~~~\n",sample, chn, frameLen,m_audioCaptureMng.recorderSize);
    m_audioCaptureMng.audioCaptureSatues = 1;
    return 0;
}

3、音频采集停止,资源释放


//关闭音频采集
int stopAudioCapatureDev()
{
    if (0 == m_audioCaptureMng.audioCaptureSatues)
    {
        return 0;
    }
    SLresult ret  = 0;
    // 停止录制
    if (m_audioCaptureMng.audioRecord != NULL)
    {
        //设置录制器为停止状态 SL_RECORDSTATE_STOPPED
        ret = (*m_audioCaptureMng.audioRecord)->SetRecordState(m_audioCaptureMng.audioRecord, SL_RECORDSTATE_STOPPED);
        if(ret < 0)
        {
            LOGE("createAudioCaptureEngine is error %d\n",ret);
            return -1;
        }
        LOGI("stop Record done\n");
    }


    // 释放资源
    if (m_audioCaptureMng.recorderObject != NULL) 
    {
        (*m_audioCaptureMng.recorderObject)->Destroy(m_audioCaptureMng.recorderObject);
        m_audioCaptureMng.recorderObject = NULL;
        m_audioCaptureMng.audioRecord = NULL;
        m_audioCaptureMng.recorderBufferQueue = NULL;
    }

    // 释放引擎对象的资源
    if (m_audioCaptureMng.engineObject != NULL) {
       
        (*m_audioCaptureMng.engineObject)->Destroy(m_audioCaptureMng.engineObject);
        m_audioCaptureMng.engineObject = NULL;
        m_audioCaptureMng.engineInterface = NULL;
    }

    if (m_audioCaptureMng.recorderBuffer)
    {
        delete [] m_audioCaptureMng.recorderBuffer;
        m_audioCaptureMng.recorderBuffer = NULL;
    }
    
    m_audioCaptureMng.audioCaptureSatues = 0;

    return 0;
}

五、openSLES播放音频代码示例

1、创建音频播放引擎

static int createAudioPlayEngine()
{
    SLEngineOption engineOptions[] = {(SLuint32) SL_ENGINEOPTION_THREADSAFE,
                                       (SLuint32) SL_BOOLEAN_TRUE};
    // 创建引擎对象,
    SLresult ret;
    ret = slCreateEngine(
            &m_audioPlayMng.engineObject, //对象地址,
            1, //配置参数数量
            engineOptions, //配置参数
            0,             //支持的接口数量
            NULL, 
            NULL
    );

    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio slCreateEngine is error %d\n",ret);
        return -1;
    }
    
    //实例化这个对象 SL_BOOLEAN_FALSE 表示即不使用异步,即使用同步,
    ret = (*m_audioPlayMng.engineObject)->Realize(m_audioPlayMng.engineObject, SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Realize is error %d\n",ret);
        return -1;
    }
    
    //从这个对象里面获取引擎接口
    (*m_audioPlayMng.engineObject)->GetInterface(m_audioPlayMng.engineObject, SL_IID_ENGINE, &m_audioPlayMng.engineInterface);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }

    return 0;
}

2、创建混合器

static int createAudioOutPutMix()
{

    int ret = (*m_audioPlayMng.engineInterface)->CreateOutputMix(m_audioPlayMng.engineInterface, //引擎接口
                                                  &m_audioPlayMng.outputMixObject, //录制对象地址,用于传出对象
                                                  0,
                                                  0, 
                                                  0  
                                                  );
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio CreateOutputMix is error %d\n",ret);
        return -1;
    }

    //实例化这个混音器对象
    ret = (*m_audioPlayMng.outputMixObject)->Realize(m_audioPlayMng.outputMixObject, SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Realize is error %d\n",ret);
        return -1;
    }

    return 0;
}

3、配置音频播放

static int initAudioPlay(int sample, int chn)
{
    int slSample = checkSampleRate(sample);
    int curChn    = chn > 2 ? 2:(chn < 1 ? 1 : chn);
    //int curFrameLenPerChn = frameLen > 2048 ? 2048 : (frameLen < 960 ? 960 : frameLen);
    int chnCfg = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
    if (curChn == 1)
    {
        chnCfg = SL_SPEAKER_FRONT_CENTER;
    }

    // 数据源简单缓冲队列定位器,输出buffer队列
    SLDataLocator_AndroidSimpleBufferQueue playBufferQueue = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, //buff类型 
            NUM_BUFFER_QUEUE //buffer的数量
    };
   // PCM 数据源格式 //设置输出数据的格式
    SLDataFormat_PCM pcmCfg = {
            SL_DATAFORMAT_PCM, //输出PCM格式的数据
            (SLuint32)curChn,  //  //输出的声道数
            (SLuint32)slSample, //输出的采样频率,
            SL_PCMSAMPLEFORMAT_FIXED_16, //输出的采样格式,这里是16bit
            SL_PCMSAMPLEFORMAT_FIXED_16,//跟随上一个参数
            (SLuint32)chnCfg,//声道配置,
            SL_BYTEORDER_LITTLEENDIAN //PCM数据的大小端排列
    };
    // 输出,SLDataSink 表示音频数据输出信息
    SLDataSource audioSrc = {
            &playBufferQueue, //SLDataFormat_PCM配置输出
            &pcmCfg //输出数据格式
    };
    
    
    SLDataLocator_OutputMix outmix = {SL_DATALOCATOR_OUTPUTMIX,m_audioPlayMng.outputMixObject};
    SLDataSink audioSink= {&outmix,0};

    const SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
    const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    
    int ret = (*m_audioPlayMng.engineInterface)->CreateAudioPlayer(m_audioPlayMng.engineInterface,
                                              &m_audioPlayMng.auidoPlay,
                                              &audioSrc, &audioSink,
                                              2, ids, req);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio CreateAudioPlayer is error %d\n",ret);
        return -1;
    }
    
    ret = (*m_audioPlayMng.auidoPlay)->Realize(m_audioPlayMng.auidoPlay,SL_BOOLEAN_FALSE);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Realize is error %d\n",ret);
        return -1;
    }

    ret = (*m_audioPlayMng.auidoPlay)->GetInterface(m_audioPlayMng.auidoPlay,SL_IID_PLAY,&m_audioPlayMng.playItf);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }

    ret = (*m_audioPlayMng.auidoPlay)->GetInterface(m_audioPlayMng.auidoPlay,SL_IID_BUFFERQUEUE,&m_audioPlayMng.playBufferQueue);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio GetInterface is error %d\n",ret);
        return -1;
    }

    ret = (*m_audioPlayMng.playBufferQueue)->RegisterCallback(m_audioPlayMng.playBufferQueue,AudioPlayCallback,0);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio RegisterCallback is error %d\n",ret);
        return -1;
    }

     //设置为播放状态
    (*m_audioPlayMng.playItf)->SetPlayState(m_audioPlayMng.playItf,SL_PLAYSTATE_PLAYING);
        if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio SetPlayState is error %d\n",ret);
        return -1;
    }
    //启动队列回调
    (*m_audioPlayMng.playBufferQueue)->Enqueue(m_audioPlayMng.playBufferQueue,"",1);
    if(SL_RESULT_SUCCESS != ret)
    {
        LOGE("audio Enqueue is error %d\n",ret);
        return -1;
    }
    LOGI("init Audio Play succeed !(%d,%d)~~~~~\n",sample, chn);

    return 0;
}

4、音频播放退出资源释放

int audioPlayDeInit()
{
    if (0 == m_audioPlayMng.bInit)
    {
        return 0;
    }
    int ret = 0;
    // 停止录制
    if (m_audioPlayMng.playItf != NULL)
    {
        //设置播放器为停止状态 SL_PLAYSTATE_STOPPED
        ret = (*m_audioPlayMng.playItf)->SetPlayState(m_audioPlayMng.playItf, SL_PLAYSTATE_STOPPED);
        if(ret < 0)
        {
            LOGE("SetRecordState is error %d\n",ret);
            return -1;
        }
    }

    // 释放资源,
    if (m_audioPlayMng.auidoPlay != NULL) 
    {
        (*m_audioPlayMng.auidoPlay)->Destroy(m_audioPlayMng.auidoPlay);
        m_audioPlayMng.auidoPlay = NULL;
        m_audioPlayMng.playItf = NULL;
        m_audioPlayMng.playBufferQueue = NULL;
    }

    if (m_audioPlayMng.outputMixObject != NULL) 
    {
        (*m_audioPlayMng.outputMixObject)->Destroy(m_audioPlayMng.outputMixObject);
        m_audioPlayMng.outputMixObject = NULL;
    }
    
    //释放引擎对象的资源
    if (m_audioPlayMng.engineObject != NULL) {
        
        (*m_audioPlayMng.engineObject)->Destroy(m_audioPlayMng.engineObject);
        m_audioPlayMng.engineObject = NULL;
        m_audioPlayMng.engineInterface = NULL;
    }

    for (size_t i = 0; i < NUM_BUFFER_QUEUE; i++)
    {
        if (m_audioPlayMng.playBuff[i])
        {
            delete [] m_audioPlayMng.playBuff[i];
            m_audioPlayMng.playBuff[i] = NULL;
        }
    }
    
    m_audioPlayMng.bInit = 0;
    LOGI("release play done\n");
    return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

NDK Android平台openSLES音频采集和播放 的相关文章

随机推荐

  • Android 10 静默安装与卸载(含源码)

    复制上面代码可以直接使用 package com taide launcher util import android app PendingIntent import android content Context import andr
  • 7.16 多益网络笔试

    在战盟客户端上进行的笔试 1 链表不具有的特点是 A 可随机访问任意元素 B 不必事先估计存储空间 C 插入数据元素时不需移动数据元素 D 删除数据元素时不需移动数据元素 A为顺序表的特点 2 栈的特点 后进先出 3 线性数据结构有哪些 线
  • 区块链-默克尔树(Merkle Tree)

    Merkle Tree 也被成为 Hash Tree 见名思意 这种树其实就是用来存储 hash 值的一种树 关于hash我们在之前的文章已经说过了 对于一个输入 都有一个唯一的长度的固定的输出 且以我们目前的科技状况 无法找到两个不同的输
  • 什么时候该采用结对编程?

    本文转载至 http www iteye com news 20082 编者按 原文作者Andriy Solovey从事软件开发已有15年 做过开发人员 软件经理和系统架构师 关注构建优质 可靠和可用的软件 结对编程是构建软件系统的一种有效
  • jmeter-本地压测-数据监控

    一 1 首先我们可以安装一个插件管理工具 Plugins Manager Plugins Manager下载地址 Install JMeter Plugins orgA custom set of plugins for Apache JM
  • Vue系列之入门篇

    前言 目录 一 关于Vue的简介 1 什么是Vue 2 使用Vue框架的好处 3 库和框架的区别 4 MVVM的介绍 5 Vue的入门案例 二 Vue的生命周期 一 关于Vue的简介 1 什么是Vue Vue是一个构建用户界面 UI 的渐进
  • Nginx做反向代理和负载均衡时“X-Forwarded-For”信息头的处理

    一 概述 如今利用nginx做反向代理和负载均衡的实例已经很多了 针对不同的应用场合 还有很多需要注意的地方 本文要说的就是在通过CDN后到达nginx做反向代理和负载均衡时请求头中的 X Forwarded For 项到底发生了什么变化
  • 电脑电池,我的笔记本电脑的电池为什么一直没电

    凡是电池都是有个使用寿命的 笔记本电池的寿命可是按照充放电的次数来计算的 但很多人对这个问题并不十分了解 加上一般笔记本电池的充放电次数起码也在500次左右 以至于很多朋友都认为笔记本电池的寿命也就是两年左右 笔记本电池即使不用 也会有自放
  • pyTorch中tensor运算

    文章目录 PyTorch的简介 PyTorch中主要的包 PyTorch的安装 使用GPU的原因 使数据在GPU上运行 什么使Tensor 张量 一些术语介绍 Tensor的属性介绍 Rank axis shape Rank Axis 轴
  • 离散引擎仿真基础

    1 简答题 Q 解释 游戏对象 GameObjects 和 资源 Assets 的区别与联系 区别 游戏对象 是Unity中的基本对象 游戏中的每个对象都是游戏对象 可以表现为人物 道具 场景等等 它们本身并不能完成很多工作 但它们的主要作
  • LeetCode(力扣)1005. K 次取反后最大化的数组和Python

    LeetCode1005 K 次取反后最大化的数组和 题目链接 代码 题目链接 https leetcode cn problems maximize sum of array after k negations 代码 class Solu
  • GPT-4只是AGI的火花?LLM终将退场,世界模型才是未来

    来源 新智元报道 编辑 润 Lumina 导读 人类距离AGI还有多远 也许大语言模型不是最终答案 一个理解世界的模型才是未来的方向 在人类的认知之中 似乎早已习惯将通用人工智能 AGI 设定为人工智能的终极形态和发展的最终目标 虽然Ope
  • python实现SHA256

    from hashlib import sha256 import hmac def get sign key data sha256加密有2种 hsobj sha256 key encode utf 8 hsobj update data
  • 基于51 手机遥控的蓝牙小车(HC-05)

    文章目录 一 软件 手机下载 蓝牙串口 电脑下载 XCOM串口调试助手 二 硬件 HC 05模块 USB转TTL模块 51小车 1 HC 05 2 USB转TTL模块 三 调试 1 引脚连接 2 进入AT模式 3 手机端串口助手的调试 4
  • C#——ref

    C ref ref 关键字指示按引用传递的值 它用在四种不同的上下文中 1 在方法签名和方法调用中 按引用将参数传递给方法 2 在方法签名中 按引用将值返回给调用方 3 在成员正文中 指示引用返回值是否作为调用方欲修改的引用被存储在本地 或
  • 发邮件向论文作者卑微求代码模板

    记录本人第一封卑微邮件 肯定不是最后一封 主题 关于XXX 随机Petri网 的实现问题 question regarding XXX the implementation of stochastic Petri nets 正文 中文版本
  • 交叉路口红绿灯控制程序linux,西门子PLC编程实例详解|十字路口交通灯自动控制系统...

    原标题 西门子PLC编程实例详解 十字路口交通灯自动控制系统 知识点和关键字 定时器 触点比较指令 传送指令 变址应用 数据块 控制要求 示意图 时序图 工艺流程图 当该路口是红灯时 另外一个路口是通行时间 绿灯亮和黄灯闪亮 当另外一个路口
  • 华为5g测试软件probe_【简讯】华为自研超高速sfs闪存曝光;苹果宣布11月10日举行新品发布会…...

    苹果宣布11月10日举行新品发布会 今天 苹果终于对外宣布 将在美国时间11月10举行新品线上发布会 而本次发布会的主题是 One more thing 至于这次发布会会带来什么新品 目前还不清楚 不过之前库克 Tim Cook 已经曝光
  • Tomcat的安装配置与使用,及常用端口大全

    如果有兴趣了解更多相关知识 可以来我的个人博客看看 eyes 的个人空间 零 Tomcat的介绍 Tomcat是Apache 软件基金会的Jakarta 项目中的一个核心项目 由Apache Sun 和其他一些公司及个人共同开发而成 由于有
  • NDK Android平台openSLES音频采集和播放

    Android平台使用openSLES采集麦克风音频代码实现 链接 https edu csdn net learn 38258 606150 spm 1003 2001 3001 4157 Android平台使用openSLES播放PCM