Android-NDK-audio-echo

Unity安卓打包失败根源:JDK/SDK/NDK三重环境链深度解析 Android SDK和JDK是安卓应用开发的基础技术栈,前者提供aapt、adb、d8等原生构建工具,后者负责Java字节码编译与APK签名;二者通过NDK延伸至C++层,共同支撑Unity的IL2CPP后端。其核心原理在于版本强约束与路径严格校验——JDK字节码版本不匹配会触发major version错误,SDK组件缺失将导致aapt2或zipalign命令未找到,NDK路径指向子目录而非根目录则无法读取source.properties。这种精密依赖构成工程落地的关键技术门槛,广泛应用于Unity游 阅读详情

项目图

运行界面

界面分析

setContentView(R.layout.activity_main);//设置布局文件

controlButton = (Button)findViewById((R.id.capture_control_button));


statusView = (TextView)findViewById(R.id.statusView);

{

private void queryNativeAudioParameters() {
    supportRecording = true;
    AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);//获取audioManager的服务
    if(myAudioMgr == null) {
        supportRecording = false;
        return;
    }
    nativeSampleRate  =  myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);//audio的采样率
    nativeSampleBufSize =myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);//每个sample的大小
{
  • 音频源:我们可以使用麦克风作为采集音频的数据源。

  • 采样率:一秒钟对声音数据的采样次数,采样率越高,音质越好。

  • 音频通道:单声道,双声道等,

  • 音频格式:一般选用PCM格式,即原始的音频样本。

  • 缓冲区大小:音频数据写入缓冲区的总数,可以通过AudioRecord.getMinBufferSize获取最小的缓冲区。(将音频采集到缓冲区中然后再从缓冲区中读取)

}

    // hardcoded channel to mono: both sides -- C++ and Java sides
    int recBufSize = AudioRecord.getMinBufferSize(
            Integer.parseInt(nativeSampleRate),
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT);
    if (recBufSize == AudioRecord.ERROR ||
            recBufSize == AudioRecord.ERROR_BAD_VALUE) {
        supportRecording = false;
    }

}

}

queryNativeAudioParameters();

delaySeekBar = (SeekBar)findViewById(R.id.delaySeekBar);
curDelayTV = (TextView)findViewById(R.id.curDelay);
echoDelayProgress = delaySeekBar.getProgress() * 1000 / delaySeekBar.getMax();

设置seekbar的监听器

delaySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        float curVal = (float)progress / delaySeekBar.getMax();
        curDelayTV.setText(String.format("%s", curVal));
{

//设置坐标

private void setSeekBarPromptPosition(SeekBar seekBar, TextView label) {
    float thumbX = (float)seekBar.getProgress()/ seekBar.getMax() *
                          seekBar.getWidth() + seekBar.getX();
    label.setX(thumbX - label.getWidth()/2.0f);
}
}
        setSeekBarPromptPosition(delaySeekBar, curDelayTV);
        if (!fromUser) return;

        echoDelayProgress = progress * 1000 / delaySeekBar.getMax();
{
static native boolean configureEcho(int delayInMs, float decay);
}
        configureEcho(echoDelayProgress, echoDecayProgress);
    }
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {}
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {}
});
//当前view 在attachedToWindow之后执行操作
delaySeekBar.post(new Runnable() {
    @Override
    public void run() {
        setSeekBarPromptPosition(delaySeekBar, curDelayTV);
    }
});
private void startEcho() {
    if(!supportRecording){
        return;
    }
    if (!isPlaying) {
        if(!createSLBufferQueueAudioPlayer()) {
            statusView.setText(getString(R.string.player_error_msg));
            return;
        }
        if(!createAudioRecorder()) {
            deleteSLBufferQueueAudioPlayer();
            statusView.setText(getString(R.string.recorder_error_msg));
            return;
        }
        startPlay();   // startPlay() triggers startRecording()
        statusView.setText(getString(R.string.echoing_status_msg));
    } else {
        stopPlay();  // stopPlay() triggers stopRecording()
        updateNativeAudioUI();
        deleteAudioRecorder();
        deleteSLBufferQueueAudioPlayer();
    }
    isPlaying = !isPlaying;
    controlButton.setText(getString(isPlaying ?
            R.string.cmd_stop_echo: R.string.cmd_start_echo));
}

 jni function 声明

/*
 * jni function declarations
 */
static native void createSLEngine(int rate, int framesPerBuf,
                                  long delayInMs, float decay);
static native void deleteSLEngine();
static native boolean configureEcho(int delayInMs, float decay);
static native boolean createSLBufferQueueAudioPlayer();
static native void deleteSLBufferQueueAudioPlayer();

static native boolean createAudioRecorder();
static native void deleteAudioRecorder();
static native void startPlay();
static native void stopPlay();

OpenSL ES

以上图片引用自:https://www.jianshu.com/p/82da5f87314f

OpensSL ES是无授权费,跨平台的,针对嵌入式精心优化的硬件音频加速API。

对象和接口的概念:

对象:提供一组资源及其状态的抽象

接口:提供特定功能的方法的抽象

对象与接口的关系:

对象暴露的接口,有以下三个方面决定:

1)对象的类型

2)应用程序在对象创建期间的,接口请求。

3)在对象声明周期,接口的添加和移除。

一个对象的类型,表明了它有implicat interfaces,implicat interface的含义是:无论应用程序是否request,它都存在对象暴露的接口函数中。

在对象创建的时候,如果有应用请求才暴露的接口,被称为explicit interfaces。

对象定义的可以动态添加和移除的接口,被称为dynamic interfaces.SLDynamicInterfaceManagementItf

EchoAudioEngine

struct EchoAudioEngine {
  SLmilliHertz fastPathSampleRate_;
  uint32_t fastPathFramesPerBuf_;
  uint16_t sampleChannels_;
  uint16_t bitsPerSample_;

  SLObjectItf slEngineObj_;
  SLEngineItf slEngineItf_;

  AudioRecorder *recorder_;
  AudioPlayer *player_;
  AudioQueue *freeBufQueue_;  // Owner of the queue
  AudioQueue *recBufQueue_;   // Owner of the queue

  sample_buf *bufs_;
  uint32_t bufCount_;
  uint32_t frameCount_;
  int64_t echoDelay_;
  float echoDecay_;
  AudioDelay *delayEffect_;
};
static EchoAudioEngine engine;

SLObjectItf:任何对象都暴露这个接口。每个方法创建一个对象,都返回这个接口SLObjectItf. 销毁对象通过destory().应用程序获取其他的接口,通过type ID 使用GetInterface来返回。通过SLObjectItf接口的Realize和resume来控制状态。

SLEngineItf:应用程序开启一个回话的方式,是通过创建一个engine对象的。Engine对象的创建是通过一个SLCreateEngine()来获取的,返回一个SLObjectItf.

当Engine对象创建后,可以获取它的SLEngineItf。

SLBufferQueueItf:被用于流式的音频数据,填充到一个player object或者record object的buffer队列里面。

1)对于recorder对象,当recorder的状态处于SL_RECORDSTATE_RECORDING时。这个对象被SLRecordItf接口控制添加buffer,来隐含开始填充的进程。如果队列中没有足够的buffer,这个auido的数据的填充将会停止和在buffer队列中的要被录制的audio数据会丢失。这个录制仍旧是SL_RECORDSTATE_RECORDING状态。一旦入队了额外的buffer,

填充音频数据将与当前音频数据一起恢复,而不是从饥饿开始了。如果recorder没有处于录制状态,额外的buffer并没有填充队列中任何buffer.

 2)在播放对象中的buffer被就地使用,并不会被设备拷贝。应用程序的开发者应该注意到,修改在已经入队的buffer的内容是无效的和会引起音频数据损坏的。

3)一旦入队列的buffer完成了播放或者填充,有callback进行通知,它是安全删除buffer。它也是安全的用新数据填充buffer,和在在播放对象入队buffer和再次入队buffer在录制对象。

4)状态转换为SL_PLAYSTATE_STOPPED,通过release所有的buffer来清空队列,和设置cursor为0.每一个buffer被释放,都会回调被带有SL_BUFFERQUEUENVENT_STOP的flag.

5)一旦转换为了SL_RECORDSTATE_STOPPED状态,应用程序应该继续放buffer到队列中,来取回系统中剩余的buffer。获取剩余的buffer完成的标志是回调方法带有了SL_BUFFERQUEUEEVNENT_CONTEN_END的事件flag。空的buffer可以被用于下一个录制的回话。录制的cursor被设置为0.

6)一旦转化为SL_PLAYSTATE_PAUSEDor SL_RECORDSTATE_PAUSED,cursor仍旧保留在当前的位置。


 

createSLEngine

engine.fastPathSampleRate_ = static_cast<SLmilliHertz>(sampleRate) * 1000;
engine.fastPathFramesPerBuf_ = static_cast<uint32_t>(framesPerBuf);
engine.sampleChannels_ = AUDIO_SAMPLE_CHANNELS;
engine.bitsPerSample_ = SL_PCMSAMPLEFORMAT_FIXED_16;
/*
SL_API SLresultSLAPIENTRY slCreateEngine(
SLObjectItf *pEngine,
SLuint32 numOptions
const SLEngineOption *pEngineOptions,
SLuint32 numInterfaces,
const SLInterfaceID *pInterfaceIds,
const SLboolean * pInterfaceRequired
)


*/


result = slCreateEngine(&engine.slEngineObj_, 0, NULL, 0, NULL, NULL);
SLASSERT(result);

//Realizing the object in synchronous mode. */

result =
    (*engine.slEngineObj_)->Realize(engine.slEngineObj_, SL_BOOLEAN_FALSE);
SLASSERT(result);

//获取slEngineItf_

result = (*engine.slEngineObj_)
             ->GetInterface(engine.slEngineObj_, SL_IID_ENGINE,
                            &engine.slEngineItf_);
// compute the RECOMMENDED fast audio buffer size:
//   the lower latency required
//     *) the smaller the buffer should be (adjust it here) AND
//     *) the less buffering should be before starting player AFTER
//        receiving the recorder buffer
//   Adjust the bufSize here to fit your bill [before it busts]
uint32_t bufSize = engine.fastPathFramesPerBuf_ * engine.sampleChannels_ *
                   engine.bitsPerSample_;
bufSize = (bufSize + 7) >> 3;  // bits --> byte
engine.bufCount_ = BUF_COUNT;
engine.bufs_ = allocateSampleBufs(engine.bufCount_, bufSize);
assert(engine.bufs_);

//创建缓冲队列,freeBufQueue是指空闲的buffer队列,主要是提供空的采样数组。recBufQueue是接收缓冲队列,主要是用来存储已采集到的音频数据,同样也是播放数据的来源。引擎初始化完毕之后会初始化freeBufQueue,初始化了16个空的大小为480字节的数组。至此音频引擎的初始化结束。

engine.freeBufQueue_ = new AudioQueue(engine.bufCount_);
engine.recBufQueue_ = new AudioQueue(engine.bufCount_);
assert(engine.freeBufQueue_ && engine.recBufQueue_);
for (uint32_t i = 0; i < engine.bufCount_; i++) {
  engine.freeBufQueue_->push(&engine.bufs_[i]);
}

//创建AudioDelay

engine.echoDelay_ = delayInMs;
engine.echoDecay_ = decay;
engine.delayEffect_ = new AudioDelay(
    engine.fastPathSampleRate_, .sampleChannels_, engine.bitsPerSample_,
    engine.echoDelay_, engine.echoDecay_);
assert(engine.delayEffect_);

AudioPlayer

构造方法

SLresult result;
assert(sampleFormat);
sampleInfo_ = *sampleFormat;
/*
SLresult (*CreateOutputMix) (
SLEngineItf self,
SLObjectItf* pMix,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
创建混音器的对象
*/
result = (*slEngine)
             ->CreateOutputMix(slEngine, &outputMixObjectItf_, 0, NULL, NULL);
SLASSERT(result);

// realize the output mix
result =
    (*outputMixObjectItf_)->Realize(outputMixObjectItf_, SL_BOOLEAN_FALSE);
SLASSERT(result);
// configure audio source,配置audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
    SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
SLAndroidDataFormat_PCM_EX format_pcm;
ConvertToSLSampleFormat(&format_pcm, &sampleInfo_);
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
// configure audio sink,配置音频的输出
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX,
                                      outputMixObjectItf_};
SLDataSink audioSnk = {&loc_outmix, NULL};
/*
 * create fast path audio player: SL_IID_BUFFERQUEUE and SL_IID_VOLUME
 * and other non-signal processing interfaces are ok.,创建audioplayer
 */
SLInterfaceID ids[2] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME};
SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
/*
SLresult (*CreateAudioPlayer) (
SLEngineItf self,
SLObjectItf* pPlayer,
const SLDataSource *pAudioSrc,
const SLDataSink *pAudioSnk,
SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired
);
创建音频播放器
*/
result = (*slEngine)->CreateAudioPlayer(
    slEngine, &playerObjectItf_, &audioSrc, &audioSnk,
    sizeof(ids) / sizeof(ids[0]), ids, req);
SLASSERT(result);
// realize the player,实现播放器
result = (*playerObjectItf_)->Realize(playerObjectItf_, SL_BOOLEAN_FALSE);
SLASSERT(result);
// get the play interface,获取播放接口
result = (*playerObjectItf_)
             ->GetInterface(playerObjectItf_, SL_IID_PLAY, &playItf_);
SLASSERT(result);
// get the buffer queue interface,获取bufferq queue的接口
result = (*playerObjectItf_)
             ->GetInterface(playerObjectItf_, SL_IID_BUFFERQUEUE,
                            &playBufferQueueItf_);
SLASSERT(result);
// register callback on the buffer queue,在bufferq queue上注册接口
result = (*playBufferQueueItf_)
             ->RegisterCallback(playBufferQueueItf_, bqPlayerCallback, this);
SLASSERT(result);

//设置播放状态

result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
SLASSERT(result);
// create an empty queue to track deviceQueue
devShadowQueue_ = new AudioQueue(DEVICE_SHADOW_BUFFER_QUEUE_LEN);
assert(devShadowQueue_);

silentBuf_.cap_ = (format_pcm.containerSize >> 3) * format_pcm.numChannels *
                  sampleInfo_.framesPerBuf_;
silentBuf_.buf_ = new uint8_t[silentBuf_.cap_];
memset(silentBuf_.buf_, 0, silentBuf_.cap_);
silentBuf_.size_ = silentBuf_.cap_;

AudioPlayer::Start

//首先获取播放状态

SLuint32 state;
SLresult result = (*playItf_)->GetPlayState(playItf_, &state);
if (result != SL_RESULT_SUCCESS) {
  return SL_BOOLEAN_FALSE;
}
if (state == SL_PLAYSTATE_PLAYING) {
  return SL_BOOLEAN_TRUE;
}
//先设置播放状态是STOPPED状态
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
SLASSERT(result);
//然后如队列相应的buffer。
result =
    (*playBufferQueueItf_)
        ->Enqueue(playBufferQueueItf_, silentBuf_.buf_, silentBuf_.size_);
SLASSERT(result);
devShadowQueue_->push(&silentBuf_);
//设置播放的状态为playing
result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_PLAYING);
SLASSERT(result);
return SL_BOOLEAN_TRUE;

ProcessSLCallback

注册callback:

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *ctx) {
  (static_cast<AudioPlayer *>(ctx))->ProcessSLCallback(bq);
}
void AudioPlayer::ProcessSLCallback(SLAndroidSimpleBufferQueueItf bq) {
#ifdef ENABLE_LOG
  logFile_->logTime();
#endif
  std::lock_guard<std::mutex> lock(stopMutex_);

  // retrieve the finished device buf and put onto the free queue
  // so recorder could re-use it,获取已经完成的device buf,并把它放到空闲队列的缓冲区。recorder可以重复使用它
  sample_buf *buf;
devShadowQueue_->pop();

  if (buf != &silentBuf_) {
    buf->size_ = 0;
    freeQueue_->push(buf);

    if (!playQueue_->front(&buf)) {
#ifdef ENABLE_LOG
      logFile_->log("%s", "====Warning: running out of the Audio buffers");
#endif
      return;
    }
devShadowQueue_->push(buf);
(*bq)->Enqueue(bq, buf->buf_, buf->size_);//在从播放队列中,获取一个buffer并入队列。
playQueue_->pop();

//如果播放队列的大小小于kickstart buffer的数量。入队列silent数据。

playQueue是播放队列,如果为空的话表示没有缓冲数据,这里回调到用的地方做错误处理,若是成功取出,那么先将其存入中转队列,并且将其传入调用播放的方法中开启播放,最后在播放队列中删除该已经播放的数组,在播放完成之后会进入Player播放队列注册的回调中。

if (playQueue_->size() < PLAY_KICKSTART_BUFFER_COUNT) {
  (*bq)->Enqueue(bq, buf->buf_, buf->size_);
  devShadowQueue_->push(&silentBuf_);
  return;
}

//填充要播放的数据。

for (int32_t idx = 0; idx < PLAY_KICKSTART_BUFFER_COUNT; idx++) {
  playQueue_->front(&buf);
  playQueue_->pop();
  devShadowQueue_->push(buf);//devshadow的queue的作用是中转队列。
  (*bq)->Enqueue(bq, buf->buf_, buf->size_);
}

AudioPlayer::stop

SLuint32 state;

  SLresult result = (*playItf_)->GetPlayState(playItf_, &state);
  SLASSERT(result);

  if (state == SL_PLAYSTATE_STOPPED) return;

  std::lock_guard<std::mutex> lock(stopMutex_);

  result = (*playItf_)->SetPlayState(playItf_, SL_PLAYSTATE_STOPPED);
  SLASSERT(result);
  (*playBufferQueueItf_)->Clear(playBufferQueueItf_);

#ifdef ENABLE_LOG
  if (logFile_) {
    delete logFile_;
    logFile_ = nullptr;
  }
#endif

AudioPlayer:~AudioPlayer

AudioPlayer::~AudioPlayer() {
  std::lock_guard<std::mutex> lock(stopMutex_);

  // destroy buffer queue audio player object, and invalidate all associated
  // interfaces
  if (playerObjectItf_ != NULL) {
    (*playerObjectItf_)->Destroy(playerObjectItf_);
  }
  // Consume all non-completed audio buffers,消耗掉所有未完成的buffer
  sample_buf *buf = NULL;
  while (devShadowQueue_->front(&buf)) {
    buf->size_ = 0;
    devShadowQueue_->pop();
    if(buf != &silentBuf_) {
      freeQueue_->push(buf);
    }
  }
  delete devShadowQueue_;

//把正在播放的队列,放audiobuffer到freeQueue
  while (playQueue_->front(&buf)) {
    buf->size_ = 0;
    playQueue_->pop();
    freeQueue_->push(buf);
  }
//销毁混音器的对象接口
  // destroy output mix object, and invalidate all associated interfaces
  if (outputMixObjectItf_) {
    (*outputMixObjectItf_)->Destroy(outputMixObjectItf_);
  }

  delete[] silentBuf_.buf_;
}

AudioRecorder

AudioRecorder::AudioRecorder

 //转为SL的格式

SLresult result;
sampleInfo_ = *sampleFormat;
SLAndroidDataFormat_PCM_EX format_pcm;
ConvertToSLSampleFormat(&format_pcm, &sampleInfo_);

// configure audio source,配置audiosource,SL_DATALOCATOR_IODEVICE Data will be generated or consumed by the
specified IO device. Note: for audio output use
the output mix.
SL_DEFAULTDEVICEID_AUDIOINPUTIdentifier denoting the set of input devicesfrom
whichthe implementation receives audio from by
default.

SLDataLocator_IODevice loc_dev = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL}; SLDataSource audioSrc = {&loc_dev, NULL};

// configure audio sink,配置audi sink
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
    SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, DEVICE_SHADOW_BUFFER_QUEUE_LEN};
SLDataSink audioSnk = {&loc_bq, &format_pcm};

//创建audiorecorder的object

// create audio recorder
// (requires the RECORD_AUDIO permission)
const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                             SL_IID_ANDROIDCONFIGURATION};
const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*slEngine)->CreateAudioRecorder(
    slEngine, &recObjectItf_, &audioSrc, &audioSnk,
    sizeof(id) / sizeof(id[0]), id, req);
SLASSERT(result);
// Configure the voice recognition preset which has no
// signal processing for lower latency.
/*

/*---------------------------------------------------------------------------*/
/* Android AudioRecorder configuration                                       */
/*---------------------------------------------------------------------------*/
 
/** Audio recording preset */
/** Audio recording preset key */
#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset")
/** Audio recording preset values */
/**   preset "none" cannot be set, it is used to indicate the current settings
 *     do not match any of the presets. */
#define SL_ANDROID_RECORDING_PRESET_NONE                ((SLuint32) 0x00000000)
/**   generic recording configuration on the platform */
#define SL_ANDROID_RECORDING_PRESET_GENERIC             ((SLuint32) 0x00000001)
/**   uses the microphone audio source with the same orientation as the camera
 *     if available, the main device microphone otherwise */
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER           ((SLuint32) 0x00000002)
/**   uses the main microphone tuned for voice recognition */
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION   ((SLuint32) 0x00000003)
/**   uses the main microphone tuned for audio communications */
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004)
/**   uses the main microphone unprocessed */
#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED         ((SLuint32) 0x00000005)
 
/*---------------------------------------------------------------------------*/
/* Android AudioPlayer configuration                                         */
/*---------------------------------------------------------------------------*/
 
/** Audio playback stream type */
/** Audio playback stream type key */
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType")
 
/** Audio playback stream type  values */
/*      same as android.media.AudioManager.STREAM_VOICE_CALL */
#define SL_ANDROID_STREAM_VOICE        ((SLint32) 0x00000000)
/*      same as android.media.AudioManager.STREAM_SYSTEM */
#define SL_ANDROID_STREAM_SYSTEM       ((SLint32) 0x00000001)
/*      same as android.media.AudioManager.STREAM_RING */
#define SL_ANDROID_STREAM_RING         ((SLint32) 0x00000002)
/*      same as android.media.AudioManager.STREAM_MUSIC */
#define SL_ANDROID_STREAM_MEDIA        ((SLint32) 0x00000003)
/*      same as android.media.AudioManager.STREAM_ALARM */
#define SL_ANDROID_STREAM_ALARM        ((SLint32) 0x00000004)
/*      same as android.media.AudioManager.STREAM_NOTIFICATION */
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
 
/*---------------------------------------------------------------------------*/
/* Android AudioPlayer and AudioRecorder configuration                       */
/*---------------------------------------------------------------------------*/
 
/** Audio Performance mode.
 * Performance mode tells the framework how to configure the audio path
 * for a player or recorder according to application performance and
 * functional requirements.
 * It affects the output or input latency based on acceptable tradeoffs on
 * battery drain and use of pre or post processing effects.
 * Performance mode should be set before realizing the object and should be
 * read after realizing the object to check if the requested mode could be
 * granted or not.
 */
/** Audio Performance mode key */
#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode")
 
/** Audio performance values */
/*      No specific performance requirement. Allows HW and SW pre/post processing. */
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000)
/*      Priority given to latency. No HW or software pre/post processing.
 *      This is the default if no performance mode is specified. */
#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001)
/*      Priority given to latency while still allowing HW pre and post processing. */
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002)
/*      Priority given to power saving if latency is not a concern.
 *      Allows HW and SW pre/post processing. */
#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003)
在采集选项中包含

xxx_RECORDING_PRESET_GENERIC(通用配置,不知道是啥意思)
xxx_RECORDING_PRESET_CAMCORDER(录像中优先使用摄像头同方向的Mic,如果没有同方向的就使用主Mic)
xxx_RECORDING_PRESET_VOICE_RECOGNITION(针对语音识别业务进行了优化,可能使用降噪Mic)
xxx_RECORDING_PRESET_VOICE_COMMUNICATION(针对电话或网络电话优化,可能会硬件AEC、NS、AGC)
xxx_RECORDING_PRESET_UNPROCESSED(使用主Mic采集,不经过任何优化处理)

在渲染选项中包含

xxx_STREAM_VOICE(VoIP或者电话,音量需要通过通话音量调节)
xxx_STREAM_SYSTEM(系统音量,我的华为P10没有这个音量选项)
xxx_STREAM_RING(铃声音量)
xxx_STREAM_MEDIA(媒体音量)
xxx_STREAM_ALARM(闹钟音量)

————————————————
版权声明:本文为优快云博主「everlastxc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_29621351/article/details/94562600
*/
SLAndroidConfigurationItf inputConfig;
result = (*recObjectItf_)
             ->GetInterface(recObjectItf_, SL_IID_ANDROIDCONFIGURATION,
                            &inputConfig);
if (SL_RESULT_SUCCESS == result) {
  SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
  (*inputConfig)
      ->SetConfiguration(inputConfig, SL_ANDROID_KEY_RECORDING_PRESET,
                         &presetValue, sizeof(SLuint32));
}
result = (*recObjectItf_)->Realize(recObjectItf_, SL_BOOLEAN_FALSE);
SLASSERT(result);

//获取recordobjectItf

result =
    (*recObjectItf_)->GetInterface(recObjectItf_, SL_IID_RECORD, &recItf_);
SLASSERT(result);

//获取对象中的bufferqueue

result = (*recObjectItf_)
             ->GetInterface(recObjectItf_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
                            &recBufQueueItf_);
SLASSERT(result);

//注册bufferqueue中的callback回调接口

result = (*recBufQueueItf_)
             ->RegisterCallback(recBufQueueItf_, bqRecorderCallback, this);
SLASSERT(result);

//创建中转的缓冲区

devShadowQueue_ = new AudioQueue(DEVICE_SHADOW_BUFFER_QUEUE_LEN);
assert(devShadowQueue_);

AudioRecorder::Start

// in case already recording, stop recording and clear buffer queue,先暂停录制和情况buffer queue队列
result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_STOPPED);
SLASSERT(result);
result = (*recBufQueueItf_)->Clear(recBufQueueItf_);
SLASSERT(result);
for (int i = 0; i < RECORD_DEVICE_KICKSTART_BUF_COUNT; i++) {
  sample_buf *buf = NULL;
  if (!freeQueue_->front(&buf)) {
    LOGE("=====OutOfFreeBuffers @ startingRecording @ (%d)", i);
    break;
  }
  freeQueue_->pop();
  assert(buf->buf_ && buf->cap_ && !buf->size_);

  result = (*recBufQueueItf_)->Enqueue(recBufQueueItf_, buf->buf_, buf->cap_);//把空闲的buffer,输入进去
  SLASSERT(result);
  devShadowQueue_->push(buf);//并把这个buffer放到这个中转队列
}

//设置录制状态为RECORDING

result = (*recItf_)->SetRecordState(recItf_, SL_RECORDSTATE_RECORDING);
SLASSERT(result);

AudioRecorder::ProcessSLCallback

assert(bq == recBufQueueItf_);
sample_buf *dataBuf = NULL;
devShadowQueue_->front(&dataBuf);
devShadowQueue_->pop();
dataBuf->size_ = dataBuf->cap_;  // device only calls us when it is really
                                 // full

callback_(ctx_, ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE, dataBuf);//callback,audio数据已经存在
recQueue_->push(dataBuf);
//重新设置空闲的buffer到如队列
sample_buf *freeBuf;
while (freeQueue_->front(&freeBuf) && devShadowQueue_->push(freeBuf)) {
  freeQueue_->pop();
  SLresult result = (*bq)->Enqueue(bq, freeBuf->buf_, freeBuf->cap_);
  SLASSERT(result);
}

Audio Delay

当信号输入进来时,使信号的输出波形比输入滞后设定的时间值比如100ms

把信号持续输入看成一个和时间相关的数据流,设定一个缓冲Buffer,大小根据delay的最大值设定,比如delay最大值为2000ms、那么对于96Khz采样频率的处理来说B u f f e r = 96 ∗ 2000 Buffer = 96 * 2000Buffer=96∗2000; Buffer的大小可能还需要加上DSP一次运算的样本点数比如128,这样最终的数据缓冲值B u f f e r = 192128 Buffer = 192128Buffer=192128;
设定一个结构体,内部含有缓冲区信号样本点输入计数器指针∗ i n P *inP∗inP和样本信号计数器输出指针∗ o u t P *outP∗outP、以及延迟设置值D l y DlyDly;
∗ o u t P *outP∗outP的值根据∗ i n P *inP∗inP和D l y DlyDly之差来确定,要注意∗ i n P *inP∗inP比D l y DlyDly小的情况,这种情况下 ∗ o u t P *outP∗outP的值应该取上一个Buffer的数据,即∗ o u t P *outP∗outP位于∗ i n P *inP∗inP上一个Buffer中而不是同属一个Buffer里面
————————————————
版权声明:本文为优快云博主「Flynn2019」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/bentengdema/article/details/102495512

AudioDelay::AudioDelay

feedbackFactor_ = static_cast<int32_t>(decayWeight_ * kFloatToIntMapFactor);
liveAudioFactor_ = kFloatToIntMapFactor - feedbackFactor_;
allocateBuffer();

allocteBuffer

//转为s

float floatDelayTime = (float)delayTime_ / kMsPerSec;
//转为1s的总帧数
float fNumFrames = floatDelayTime * (float)sampleRate_ / kMsPerSec;
//总帧数
size_t sampleCount = static_cast<uint32_t>(fNumFrames + 0.5f) * channelCount_;

//字节数

uint32_t bytePerSample = format_ / 8;
assert(bytePerSample <= 4 && bytePerSample);

//每帧的字节数

uint32_t bytePerFrame = channelCount_ * bytePerSample;
// get bufCapacity in bytes
bufCapacity_ = sampleCount * bytePerSample;
bufCapacity_ =
    ((bufCapacity_ + bytePerFrame - 1) / bytePerFrame) * bytePerFrame;//按每帧的字节数对齐

buffer_ = new uint8_t[bufCapacity_];
assert(buffer_);

memset(buffer_, 0, bufCapacity_);
curPos_ = 0;

// bufSize_ is in Frames ( not samples, not bytes )
bufSize_ = bufCapacity_ / bytePerFrame;

AudioDelay::process

process() filter live audio with "echo" effect:
*   delay time is run-time adjustable
*   decay time could also be adjustable, but not used
*   in this sample, hardcoded to .5
*
* @param liveAudio is recorded audio stream
* @param channelCount for liveAudio, must be 2 for stereo
* @param numFrames is length of liveAudio in Frames ( not in byte )
// process every sample,处理每一帧
int32_t sampleCount = channelCount_ * numFrames;
int16_t* samples = &static_cast<int16_t*>(buffer_)[curPos_ * channelCount_];
for (size_t idx = 0; idx < sampleCount; idx++) {
#if 1
    int32_t curSample =
        (samples[idx] * feedbackFactor_ + liveAudio[idx] * liveAudioFactor_) /
        kFloatToIntMapFactor;
//当前帧和上一阵的根据相应的因素的叠加
    if (curSample > SHRT_MAX)
      curSample = SHRT_MAX;
    else if (curSample < SHRT_MIN)
      curSample = SHRT_MIN;
 
    liveAudio[idx] = samples[idx];
    samples[idx] = static_cast<int16_t>(curSample);
#else
    // Pure delay
    int16_t tmp = liveAudio[idx];
    liveAudio[idx] = samples[idx];
    samples[idx] = tmp;
#endif
  }

  curPos_ += numFrames;
  lock_.unlock();

Audio Common

void ConvertToSLSampleFormat(SLAndroidDataFormat_PCM_EX* pFormat,
                             SampleFormat* pSampleInfo_) {
// Only support 2 channels,设置声道数
// For channelMask, refer to wilhelm/src/android/channels.c for details
if (pSampleInfo_->channels_ <= 1) {
  pFormat->numChannels = 1;
  pFormat->channelMask = SL_SPEAKER_FRONT_LEFT;
} else {
  pFormat->numChannels = 2;
  pFormat->channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
}
case SL_ANDROID_PCM_REPRESENTATION_FLOAT:
  pFormat->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32;//设置每一帧的bit数
  pFormat->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32;//设置container的大小
  pFormat->formatType = SL_ANDROID_DATAFORMAT_PCM_EX;//设置数据类型
  break;

audio_main

分别创建audioplayer和audio record,同时注册了

engine.player_->SetBufQueue(engine.recBufQueue_, engine.freeBufQueue_);
engine.player_->RegisterCallback(EngineService, (void *)&engine);
engine.recorder_->SetBufQueues(engine.freeBufQueue_, engine.recBufQueue_);
engine.recorder_->RegisterCallback(EngineService, (void *)&engine);
bool EngineService(void *ctx, uint32_t msg, void *data) {
  assert(ctx == &engine);
  switch (msg) {
    case ENGINE_SERVICE_MSG_RETRIEVE_DUMP_BUFS: {
      *(static_cast<uint32_t *>(data)) = dbgEngineGetBufCount();
      break;
    }
    case ENGINE_SERVICE_MSG_RECORDED_AUDIO_AVAILABLE: {//当有录制的audio数据时
      // adding audio delay effect
      sample_buf *buf = static_cast<sample_buf *>(data);
      assert(engine.fastPathFramesPerBuf_ ==
             buf->size_ / engine.sampleChannels_ / (engine.bitsPerSample_ / 8));
      engine.delayEffect_->process(reinterpret_cast<int16_t *>(buf->buf_),
                                   engine.fastPathFramesPerBuf_);//达到延迟的效果
      break;
    }
    default:
      assert(false);
      return false;
  }

  return true;
}

 

 

Unity安卓打包环境配置革新:5分钟免环境变量构建APK指南 环境变量是操作系统用于指定程序运行环境的关键配置,其原理是通过预设路径让应用程序快速定位依赖库与工具链。在软件开发领域,尤其是跨平台构建场景中,环境配置的复杂性常成为效率瓶颈。传统Unity安卓打包需手动设置JAVA_HOME、ANDROID_HOME等全局变量,存在配置繁琐、环境冲突、协作困难等痛点。通过项目级环境内嵌与路径指向技术,开发者可将JDK、Android SDK/NDK等工具链集成至项目目录或Unity私有路径,实现环境隔离与标准化。这种技术方案大幅降低了Unity移动开发门槛,特别适合快速原 阅读详情

相关推荐

Android12之OpenSL ES创建引擎对象slCreateEngine(十一)

Android引入OpenSL ES是为了提高音频性能,可以让音频能使用硬件资源,帮助分担CPU的负担,很多芯片厂商会有自己独立的ADSP芯片,如高通、MTK等。此系列深入分析OpenSL ES/AAudio/OBOE和Android深层次的关系及原理.。

Android系统攻城狮 739

Android NDK开发详解高性能音频之音频延迟

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 activity 和访问实体设备组件,例如传感器和触控输入。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用

hnjzfwy的博客 480

Android Speex AEC 集成实战:NDK 编译、采样率对齐与重采样规避

回音消除(AEC)是实时语音通信的基础信号处理技术,其核心原理是通过自适应滤波器建模扬声器到麦克风的声学路径,并从采集信号中减去估计的回音分量。在 Android 平台,由于系统音频栈存在隐式重采样、缓冲区错位和浮点/定点运算不兼容等问题,通用 AEC 方案常出现崩溃、失真或失效。Speex AEC 作为轻量级定点实现,具备低延迟、高可控性优势,但需手动编译裁剪、严格匹配采样率与帧长,并绕过 AudioTrack 等高级 API 的不可控变换。本文聚焦 Android NDK 环境下 Speex AEC 的

weixin_33853794的博客 241

google官方android ndk demo解析(一)——audio echo

最近重新学习ndk,主要关注使用android studio进行ndk开发。google官方的demo无疑是最好的学习源码。 首先分析audio echo这个项目。 一、CMakeList.txt cmake_minimum_required(VERSION 3.4.1) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -W

hunter800421的专栏 3515

深入分析大众点评 Ajax 接口:直接请求 JSON 数据高效获取评论

大众点评网站采用前后端分离架构,通过Ajax接口动态加载数据。按F12打开开发者工具,切换到Network(网络)面板。实际环境中,我们需要使用Python重现这一加密逻辑。查看请求响应,寻找包含评论数据的JSON接口。参数是大众点评的反爬机制核心,需要特别处理。:尊重网站的robots.txt文件规定。:仅将数据用于合法合规的研究和分析目的。:不侵犯大众点评的商业秘密和知识产权。打开大众点评网站,进入任意商户页面。:对采集到的用户信息进行脱敏处理。:避免对目标网站造成过大负担。

Z_suger7的博客 1583

从打印攻击到视频重放:手把手用OULU-NPU训练你的第一个活体检测模型

本文详细介绍了如何使用OULU-NPU数据集训练活体检测模型,以应对打印攻击和视频重放等安全威胁。通过PyTorch框架,从环境准备、数据加载、预处理到模型架构设计,手把手教你构建一个高效的双流网络,结合CNN和LSTM技术,提升模型在多种攻击场景下的检测准确率。

weixin_29092031的博客 385

Android通过OpenSL ES播放音频套路详解

https://blog.csdn.net/ywl5320/article/details/78503768 我的视频课程(基础):《(NDK)FFmpeg打造Android万能音频播放器》 我的视频课程(进阶):《(NDK)FFmpeg打造Android视频播放器》 我的视频课程(编码直播推流):《Android视频编码和直播推流》   先定个小目标         通过本篇博客,我们会...

Linux Android Web 584

GD32F303驱动WS2812B灯带:用TIMER+PWM+DMA解放CPU,实现流畅灯效(附完整代码)

本文详细介绍了基于GD32F303的TIMER+PWM+DMA全自动控制方案,用于高效驱动WS2812B灯带。通过硬件外设协同工作,该方案不仅解放了CPU资源,还能实现复杂的灯光效果,如渐变、流水等。文章提供了完整的代码实现和性能优化技巧,适合嵌入式开发者参考。

weixin_42561143的博客 379

分析OpenSL回声Demo

分析OpenSL回声demo

BlueBerry的专栏 595

QKD(量子密钥分发)

量子密钥分发(Quantum Key Distribution,QKD)是一种基于量子力学原理的安全通信协议,它允许两个用户通过不安全的通信信道创建并共享一个安全的密钥。这个密钥随后可以用于加密和解密信息,确保通信的机密性。QKD的核心原理是量子不可克隆定理和量子测量的不确定性。

qq_40844444的博客 2896

SVPWM分析、各个扇区详细计算以及Matlab仿真

SVPWM分析以及各个扇区详细计算以及Matlab仿真说明目的两电平逆变器拓扑空间矢量扇区判断扇区内如何发波?矢量作用时间计算矢量切换时间的计算Simulink仿真/基于Matlab-R2014a版本 说明 第一次发文章,不太会用Markdown,word编辑的公式复制过来有些是乱码,因此都贴图了。 另外,本文公式和图片比较多,编辑工作量比较大,比较辛苦,转载的话,请注明出处,谢谢。 目的 由于在...

michaelf的博客 12万+

Android中的OpenSL ES是如何实现的?

一般理解的Android Audio架构是这样的: 时间长了大家也就比较认可和熟悉了,但是了解OpenSL ES之后,发现如果使用OpenSL ES,是不使用JAVA API的,那么Android系统中是哪一层实现了OpenSL ES的呢?只要搜索OpenSL ES就会搜索到这个库如何如何在Android上使用,好的文章一大堆,比如这个 Android音频开发(6):使用OpenSL ES A

Android/Linux的专栏 5589

2025_NIPS_Truthful Aggregation of LLMs with an Application to Online Advertising

大型语言模型(LLMs)生成内容的 revenue 变现是在线广告的下一个前沿领域。我们考虑这样一种场景:广告商旨在影响LLM的回复,而平台则寻求最大化广告商价值并确保用户满意度。核心挑战在于,广告商的偏好通常与用户偏好存在冲突,且广告商可能误报其偏好。为解决这一问题,我们提出MOSAIC——一种拍卖机制,它确保真实申报是广告商的占优策略,并使每个广告商的效用与其对社会福利的贡献保持一致。重要的是,该机制无需LLM微调或访问模型权重,且随着计算资源的增加,可证明收敛到最优微调LLM的输出。

c_cpp_csharp的专栏 81

音频之Android NDK读写声卡

AUDIO_FORMAT_PCM_16_BIT = 0x1u, // (PCM | PCM_SUB_16_BIT) //PCM16位。AUDIO_FORMAT_PCM_8_BIT = 0x2u, // (PCM | PCM_SUB_8_BIT) //PCM 8位。AUDIO_SOURCE_VOICE_UPLINK = 2, //Voice call uplink (Tx) audio source 语音呼叫上行(Tx)输入源。...

szkbsgy的专栏 4728

AMBA-CHI协议详解(七)

本节描述该协议包含的支持系统保序需求的机制。

Crazzy_M的博客 1158

从零开始学 AUTOSAR:深入浅出 SAE J1939 诊断通信管理器(J1939Dcm)深度完整版

SAE J1939 是美国汽车工程师学会制定的重型车辆 CAN 总线通信标准,覆盖卡车、客车、工程机械、农机、船舶等,是商用车领域的 “通用语言”。基于CAN 2.0B 扩展帧(29 位 ID)(车规主流)PGN(参数组号)+ SPN(可疑参数号)核心标准:J1939-21:数据链路层(CAN 帧格式、地址声明)J1939-71:应用层(普通信号、报文)诊断层(核心!J1939Dcm 依据)J1939-81:网络管理(地址管理、节点检测)

getusushu的博客 117

10分钟跑通ndk-samples的25个官方示例:Android NDK快速上手指南

**ndk-samples** 是 Android 官方维护的 NDK(Native Development Kit)示例仓库,收录了 25 个可直接用 Android Studio 打开运行的官方示例应用,覆盖 JNI、OpenGL、Vulkan、音频、MIDI、神经网络 API 等原生开发场景。无论你是第一次接触 Android 原生开发,还是想为项目寻找 JNI 与 CMake 的参考实现,

gitblog_00315的博客 1070

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

安卓ndk学习

官方的ndk demo还是蛮好的,看了几个,比如 audio-echo ,对于学习C++ 和 了解安卓C层都是比较不错的

u011096727的博客 215

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Android NDK Samples:官方 NDK 示例仓库的构建、运行与架构全解析

> 本文以 [README.md](https://link.gitcode.com/i/a3de4249d0a475d4d4bf466f89a0c670) 为核心,结合 [ARCHITECTURE.md](https://link.gitcode.com/i/9aeb0216e865a9b94810e2d09538f34c)、[settings.gradle](https://link.gitc

gitblog_01087的博客 166

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Android Audio High Performance入门:5分钟上手AAudio API开发音频应用

想要在Android平台上开发高性能音频应用吗?🤔 面对复杂的音频处理需求,传统的AudioTrack和OpenSL ES可能无法满足低延迟要求。别担心!Google推出的AAudio API正是为解决这一问题而生。本文将为您提供一个快速入门指南,帮助您在5分钟内掌握Android高性能音频开发的核心要点。 ## 🎯 什么是AAudio API? AAudio是Android O(API

gitblog_00458的博客 421

android echo,GitHub - gvsumasl/EchoprintForAndroid: Code you need to get Echoprint working on Androi...

Android EchoprintCode from the MASL blog post available hereEchoprint is an open source library to fingerprint Audio, in this repo you'll find the code needed to make it run under Android.This repo co...

weixin_30673943的博客 147

Android Audio High Performance与Oboe库:为什么它仍是高性能音频开发的首选参考

在移动应用开发领域,**Android音频高性能开发**一直是开发者面临的挑战之一。随着用户对音频体验要求的不断提高,低延迟、高质量的音频处理变得至关重要。本文将深入探讨Android音频高性能开发的核心概念,并重点介绍为什么**Oboe库**仍然是当前高性能音频开发的首选参考方案。 ## 🎯 什么是Android音频高性能开发? Android音频高性能开发指的是在Android平台上实现

gitblog_00971的博客 413

如何构建低延迟音频应用?Android Audio High Performance项目核心技术解析

在移动音频应用开发领域,**低延迟音频处理**是专业音乐制作、实时语音通信和游戏音效等应用的核心需求。Android Audio High Performance项目为我们提供了构建高性能音频应用的完整技术栈和最佳实践指南。本文将深入解析该项目的核心技术,帮助开发者掌握构建低延迟Android音频应用的关键技能。🎵 ## 什么是Android低延迟音频技术? **低延迟音频处理**是指音频信

gitblog_00664的博客 376

audio speex 的应用

speex 是一个免费的开源库,主要用于音频的编解码和回声消除等功能,有着较高的压缩比率,是使用于做音频传输功能,回声消除可以降低使用过程中的噪音。 1.github 源码地址 https://github.com/xiph/speex 2.主要函数 int speex_encode(void *state, float *in, SpeexBits *bits) int speex_decode(void *state, SpeexBits *bits, float *out) void

chgh1306的专栏 531

java speex回声消除_android – Speex回音消除配置

我正在使用其AudioRecord和AudioTrack类制作Android-to-Android VoIP(扬声器)应用程序,以及通过NDK的Speex进行回声消除.我能够成功传入并从Speex的speex_echo_cancellation()函数中检索数据,但回声仍然存在.这是记录/发送和接收/播放音频的相关android线程代码://constructorpublic MyThread(D...

weixin_42554151的博客 655

在 Dart SDK 中为 Android 构建独立 Dart VM:从环境准备、交叉编译到设备部署的完整指南

本指南以 Dart SDK 官方文档 [docs/Building-the-Dart-VM-for-Android.md](https://link.gitcode.com/i/cca35bfd98b53444f98d1b32757cc4cd) 为主体,系统讲解如何在 x64 Linux 或 Mac 宿主机上交叉编译出可在 Android 设备与模拟器上运行的独立 Dart VM(standalo

gitblog_00726的博客 81
上一篇: Android-NDK:native-codec
下一篇: Android-NDK-EGL
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值