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

Android MIC 口的音频采集 本文主要针对谈论如何采集音频流,用MIC口的音频流采集做实例,以及一些采集过程中需要注意到的事项。 阅读详情

最近重新学习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 -Werror")

add_library(echo SHARED
            audio_common.cpp
            audio_main.cpp
            audio_player.cpp
            audio_recorder.cpp
            debug_utils.cpp)

# include libraries needed for hello-jni lib
target_link_libraries(echo
                      android
                      atomic
                      log
                      OpenSLES)

# Android Studio 2.2.0 with CMake support does not pack stl shared libraries,
# so app needs to pack the right shared lib into APK. This sample uses solution
# from https://github.com/jomof/ndk-stl to find the right stl shared lib to use
# and copy it to the right place for Android Studio to pack
# Usage: download ndk-stl-config.cmake into app's directory hosting CMakeLists.txt
#        and just use it with the following line
include(ndk-stl-config.cmake)

cmake_minimum_required(VERSION 3.4.1)声明要求最低的cmake版本是3.4.1。接下来,
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Werror")
设置了cmake的编译参数,要求打开所有警告,并且将所有警告当做错误处理。同时声明,使用标准c++11进行编译。
add_library(echo SHARED
            audio_common.cpp
            audio_main.cpp
            audio_player.cpp
            audio_recorder.cpp
            debug_utils.cpp)
声明,由这个源代码文件编译生成动态库echo。
# include libraries needed for hello-jni lib
target_link_libraries(echo
                      android
                      atomic
                      log
                      OpenSLES)
这里声明要链接的库,这里包括上面编码生成的echo,以及用到的音频库openSLES,android系统,log库,等等。
# Android Studio 2.2.0 with CMake support does not pack stl shared libraries,
# so app needs to pack the right shared lib into APK. This sample uses solution
# from https://github.com/jomof/ndk-stl to find the right stl shared lib to use
# and copy it to the right place for Android Studio to pack
# Usage: download ndk-stl-config.cmake into app's directory hosting CMakeLists.txt
#        and just use it with the following line
include(ndk-stl-config.cmake)
如注释所说,这里要告诉app引入正确的stl库,因为Android Studio 2.2.0的CMake本身没有包含STL。

二、MainActivity.java

/*
 * Copyright 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.sample.echo;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity
        implements ActivityCompat.OnRequestPermissionsResultCallback {
    private static final int AUDIO_ECHO_REQUEST = 0;
    Button   controlButton;
    TextView statusView;
    String  nativeSampleRate;
    String  nativeSampleBufSize;
    boolean supportRecording;
    Boolean isPlaying = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        controlButton = (Button)findViewById((R.id.capture_control_button));
        statusView = (TextView)findViewById(R.id.statusView);
        queryNativeAudioParameters();

        // initialize native audio system
        updateNativeAudioUI();
        if (supportRecording) {
            createSLEngine(Integer.parseInt(nativeSampleRate), Integer.parseInt(nativeSampleBufSize));
        }
    }
    @Override
    protected void onDestroy() {
        if (supportRecording) {
            if (isPlaying) {
                stopPlay();
            }
            deleteSLEngine();
            isPlaying = false;
        }
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void startEcho() {
        if(!supportRecording){
            return;
        }
        if (!isPlaying) {
            if(!createSLBufferQueueAudioPlayer()) {
                statusView.setText(getString(R.string.error_player));
                return;
            }
            if(!createAudioRecorder()) {
                deleteSLBufferQueueAudioPlayer();
                statusView.setText(getString(R.string.error_recorder));
                return;
            }
            startPlay();   // this must include startRecording()
            statusView.setText(getString(R.string.status_echoing));
        } else {
            stopPlay();  //this must include stopRecording()
            updateNativeAudioUI();
            deleteAudioRecorder();
            deleteSLBufferQueueAudioPlayer();
        }
        isPlaying = !isPlaying;
        controlButton.setText(getString((isPlaying == true) ?
                R.string.StopEcho: R.string.StartEcho));
    }
    public void onEchoClick(View view) {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) !=
                                               PackageManager.PERMISSION_GRANTED) {
            statusView.setText(getString(R.string.status_record_perm));
            ActivityCompat.requestPermissions(
                    this,
                    new String[] { Manifest.permission.RECORD_AUDIO },
                    AUDIO_ECHO_REQUEST);
            return;
        }
        startEcho();
    }

    public void getLowLatencyParameters(View view) {
        updateNativeAudioUI();
        return;
    }

    private void queryNativeAudioParameters() {
        AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        nativeSampleRate  =  myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
        nativeSampleBufSize =myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
        int recBufSize = AudioRecord.getMinBufferSize(
                Integer.parseInt(nativeSampleRate),
                AudioFormat.CHANNEL_IN_MONO,
                AudioFormat.ENCODING_PCM_16BIT);
        supportRecording = true;
        if (recBufSize == AudioRecord.ERROR ||
            recBufSize == AudioRecord.ERROR_BAD_VALUE) {
            supportRecording = false;
        }
    }
    private void updateNativeAudioUI() {
        if (!supportRecording) {
            statusView.setText(getString(R.string.error_no_mic));
            controlButton.setEnabled(false);
            return;
        }

        statusView.setText("nativeSampleRate    = " + nativeSampleRate + "\n" +
                "nativeSampleBufSize = " + nativeSampleBufSize + "\n");

    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        /*
         * if any permission failed, the sample could not play
         */
        if (AUDIO_ECHO_REQUEST != requestCode) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            return;
        }

        if (grantResults.length != 1  ||
            grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            /*
             * When user denied permission, throw a Toast to prompt that RECORD_AUDIO
             * is necessary; also display the status on UI
             * Then application goes back to the original state: it behaves as if the button
             * was not clicked. The assumption is that user will re-click the "start" button
             * (to retry), or shutdown the app in normal way.
             */
            statusView.setText(getString(R.string.error_no_permission));
            Toast.makeText(getApplicationContext(),
                    getString(R.string.prompt_permission),
                    Toast.LENGTH_SHORT).show();
            return;
        }

        /*
         * When permissions are granted, we prompt the user the status. User would
         * re-try the "start" button to perform the normal operation. This saves us the extra
         * logic in code for async processing of the button listener.
         */
        statusView.setText("RECORD_AUDIO permission granted, touch " +
                           getString(R.string.StartEcho) + " to begin");


        // The callback runs on app's thread, so we are safe to resume the action
        startEcho();
    }

    /*
     * Loading our Libs
     */
    static {
        System.loadLibrary("echo");
    }

    /*
     * jni function implementations...
     */
    public static native void createSLEngine(int rate, int framesPerBuf);
    public static native void deleteSLEngine();

    public static native boolean createSLBufferQueueAudioPlayer();
    public static native void deleteSLBufferQueueAudioPlayer();

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

首先,看onCreate方法中,
queryNativeAudioParameters()方法,通过AudioManager,获取输出的采样率,以及输出缓冲区的大小。
通过AudioRecord,获取要求的最小的输出缓冲区的大小;如果发生错误,将标记系统不支持录音。
updateNativeAudioUI()方法来更新UI,分为支持和不支持录音两种情况。
在button控件上注册了点击事件,当点击button时,触发调用onEchoClick(方法),方法首先检查是否有录音权限,
如果没有尝试申请该权限;如果已经具有了录音权限,则会调用startEcho()方法。startEcho首先检查是否支持录音,如果不支持,
直接返回。如果系统支持录音,则判断是否有播放正在进行中。加入没有播放正在进行,则调用native方法
createSLBufferQueueAudioPlayer(),该函数的具体功能在下文分析。
如果该方法调用成功,则会继续调用native函数
createAudioRecorder(),创建recorder,如果创建失败,则调用native函数
deleteSLBufferQueueAudioPlayer()
如果一切顺利,那么调用native函数startPlay(),播放音频。如果没有音频正在播放,则一次调用native函数stopPlay(),
updateNativeAudioUI(),deleteAudioRecorder(),deleteSLBufferQueueAudioPlayer()。之后,将isPlaying的值置反。

三、native方法分析

createSLEngine(int rate, int framesPerBuf)

createSLEngine(int rate, int framesPerBuf)在audio_main.cpp中定义,其源码如下:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_createSLEngine(
        JNIEnv *env, jclass type, jint sampleRate, jint framesPerBuf) {
    SLresult result;
    memset(&engine, 0, sizeof(engine));

    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;

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

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

    result = (*engine.slEngineObj_)->GetInterface(engine.slEngineObj_, SL_IID_ENGINE, &engine.slEngineItf_);
    SLASSERT(result);

    // 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 recordered 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_);

    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]);
    }
}

首先创建一个SLresult对象result,然后将变量engine(类型为static EchoAudioEngine)的内存清空。然后设置engine中的属性:fastPathSampleRate_,fastPathFramesPerBuf_,sampleChannels_,bitsPerSample_。之后调用函数slCreateEngine()创建engine,宏SLASSERT(result)assert result的值。之后依次调用engine中成员slEngineObj_的方法Realize()以及GetInterface()。之后,计算buffer size,首先计算出bit,然后右移三位,转换成byte。BUF_COUNT的值为16,默认buffer的数量。分配BUF_COUNT个buffer size的buffer,使engine.bufs_指向这块区域。然后,分别创建freeBufQueue_和recBufQueue_。之后,将engine.buf_中的各个buffer压入engine.freeBufQueue_。

deleteSLEngine()

deleteSLEngine()方法定义在audo_main.cpp中:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_deleteSLEngine(JNIEnv *env, jclass type) {
    delete engine.recBufQueue_;
    delete engine.freeBufQueue_;
    releaseSampleBufs(engine.bufs_, engine.bufCount_);
    if (engine.slEngineObj_ != NULL) {
        (*engine.slEngineObj_)->Destroy(engine.slEngineObj_);
        engine.slEngineObj_ = NULL;
        engine.slEngineItf_ = NULL;
    }
}
首先,释放engine中的refBufQuere_和freeBufQueue。然后调用releaseSampleBufs()方法。releaseSampleBufs()是内联函数,其定义如下:

__inline__ void releaseSampleBufs(sample_buf* bufs, uint32_t& count) {
    if(!bufs || !count) {
        return;
    }
    for(uint32_t i=0; i<count; i++) {
        if(bufs[i].buf_) delete [] bufs[i].buf_;
    }
    delete [] bufs;
}

该方法逐一释放掉bufs列表中的每一个smaple_buf对象的buf_成员。然后,销毁engin中的engine.slEngineObj_成员,并将engine.slEngineObj_和engine.slEngineItf_置为NULL。

createSLBufferQueueAudioPlayer()

方法createSLBufferQueueAudioPlayer()定义在audio_main.cpp中:

JNIEXPORT jboolean JNICALL
Java_com_google_sample_echo_MainActivity_createSLBufferQueueAudioPlayer(JNIEnv *env, jclass type) {
    SampleFormat sampleFormat;
    memset(&sampleFormat, 0, sizeof(sampleFormat));
    sampleFormat.pcmFormat_ = (uint16_t)engine.bitsPerSample_;
    sampleFormat.framesPerBuf_ = engine.fastPathFramesPerBuf_;

    // SampleFormat.representation_ = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
    sampleFormat.channels_ = (uint16_t)engine.sampleChannels_;
    sampleFormat.sampleRate_ = engine.fastPathSampleRate_;

    engine.player_ = new AudioPlayer(&sampleFormat, engine.slEngineItf_);
    assert(engine.player_);
    if(engine.player_ == nullptr)
        return JNI_FALSE;

    engine.player_->SetBufQueue(engine.recBufQueue_, engine.freeBufQueue_);
    engine.player_->RegisterCallback(EngineService, (void*)&engine);

    return JNI_TRUE;
}

首先创建SampleFormat对象sampleFormat,之后调用memset清空sampleFormat内存。SampleFormat的成员pcmFormat定义了pcm采样率,这里用engine的bitsPerSample_来初始化它;用engine的fastpathFramesPerBuf_来初始化sampleFormat的framesPerBuf_。接下来分别用engine的sampleChannels和fastPathSampleRate_初始化sampleFormat的channels和smpleRate。之后,以smpleFormat和engine的slEngineItf为参数,创建AudiPlayer,并使engine的player_

指向该对象。如果创建失败,那么函数返回JNI_FALSE。如果成功,那么接下来依次调用engine的player_成员的SetBufQueue和RegisterCallback方法,设置缓冲区队列和回调。最后,方法发挥JNI_TRUE。

deleteSLBufferQueueAudioPlayer()

deleteSLBufferQueueAudioPlayer()方法定义在audio_main.cpp中:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_deleteSLBufferQueueAudioPlayer(JNIEnv *env, jclass type) {
    if(engine.player_) {
        delete engine.player_;
        engine.player_= nullptr;
    }
}

如果engine的player_成员不为空,那么就释放player_所占的内存控件,并且使player_指向nullptr。

createAudioRecorder()

createAudioRecorder()定义在audio_main.cpp中:

JNIEXPORT jboolean JNICALL
Java_com_google_sample_echo_MainActivity_createAudioRecorder(JNIEnv *env, jclass type) {
    SampleFormat sampleFormat;
    memset(&sampleFormat, 0, sizeof(sampleFormat));
    sampleFormat.pcmFormat_ = static_cast<uint16_t>(engine.bitsPerSample_);

    // SampleFormat.representation_ = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
    sampleFormat.channels_ = engine.sampleChannels_;
    sampleFormat.sampleRate_ = engine.fastPathSampleRate_;
    sampleFormat.framesPerBuf_ = engine.fastPathFramesPerBuf_;
    engine.recorder_ = new AudioRecorder(&sampleFormat, engine.slEngineItf_);
    if(!engine.recorder_) {
        return JNI_FALSE;
    }
    engine.recorder_->SetBufQueues(engine.freeBufQueue_, engine.recBufQueue_);
    engine.recorder_->RegisterCallback(EngineService, (void*)&engine);
    return JNI_TRUE;
}
首先创建SampleFormat对象sampleFormat,然后清空sampleFormat所占内存空间。然后用engine的bitsPerSample_成员初始化sampleFormat的pcmFormat_成员,分别把engine的sampleChannels,fastPathSampleRate_,fastPathFramesPerBuf_赋值给sampleFormat的channels_,sampleRate_和framesPerBuf_。然后,创建AudioRecorder的对象,并且赋值给engine的recorder_成员。如果创建失败,返回JNI_FALSE;如果创建发成功,继续调用recorder_的setBufQueues方法,设置audioRecorder_的freeBufQueue_和refBufQueue_。最后,调用recorder_的RegisterCallback方法,设置callback(callback是一个函数指针)。至此,函数返回JNI_TRUE。

deleteAudioRecorder()

deleteAudioRecorder()在audio_main.cpp中定义:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_deleteAudioRecorder(JNIEnv *env, jclass type) {
    if(engine.recorder_)
        delete engine.recorder_;

    engine.recorder_ = nullptr;
}

如果engine的recorder_成员不为空,就释放recorder_所占空间,然后使recorder_指向nullprt。

startPlay()

startPlay()定义在audio_main.cpp中:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_startPlay(JNIEnv *env, jclass type) {

    engine.frameCount_  = 0;
    /*
     * start player: make it into waitForData state
     */
    if(SL_BOOLEAN_FALSE == engine.player_->Start()){
        LOGE("====%s failed", __FUNCTION__);
        return;
    }
    engine.recorder_->Start();
}

首先,将engine的frameCount_成员初始化为0;然后,调用engine中play_成员的Start()方法,如果调用返回SL_BOOLEAN_FALSE,记录日志,并返回;如果成功,接下来调用engine中recorder_成员的Start()方法。

stopPlay()

stopPlay()定义在audio_main.cpp中:

JNIEXPORT void JNICALL
Java_com_google_sample_echo_MainActivity_stopPlay(JNIEnv *env, jclass type) {
    engine.recorder_->Stop();
    engine.player_ ->Stop();

    delete engine.recorder_;
    delete engine.player_;
    engine.recorder_ = NULL;
    engine.player_ = NULL;
}
依次调用enging中recorder_和player_成员的Stop()方法,然后删除recorder_和player_,并且使他们指向NULL。
Gemma 4手机端断网本地AI部署实战指南 本地AI是指模型在终端设备上离线运行的技术范式,其核心原理是通过模型量化、架构剪枝与推理引擎深度优化,在资源受限的移动芯片上实现低延迟、低功耗、高确定性的推理。技术价值在于突破网络依赖,保障工业、农业、医疗等弱网/无网场景下的AI可用性与数据隐私合规性。典型应用场景包括产线故障诊断、田间病虫害指导、基层村医辅助问诊等。本文聚焦Gemma 4在安卓手机的端侧落地,详解INT4量化、AWQ校准、llama.cpp定制编译、JNI内存零拷贝及断网交互重构等关键实践,覆盖从模型转换、NDK构建到真机调优的全链路。 阅读详情

相关推荐

Gemma 4 31B端侧部署实战:稀疏激活与分层量化如何实现亚秒响应

大语言模型(LLM)的端侧部署正从概念走向落地,其核心在于突破‘参数规模—计算开销—硬件限制’的传统三角约束。Gemma 4通过动态稀疏激活机制(如token级MoE路由)和分层量化策略(FP16注意力+Q4_K_M前馈+NF4嵌入),显著降低等效计算量与显存占用,使31B模型在消费级设备上达成亚秒级首字延迟与稳定多轮对话能力。这一技术路径不仅提升了本地推理效率与隐私安全性,更支撑起法律审查、嵌入式诊断、离线教育等高价值场景。结合Ollama、Dify等开源工具链,Gemma 4已成为当前最成熟的开源端侧A

weixin_33720956的博客 535

Android-NDK-audio-echo

项目图 运行界面 界面分析

CodeMyDream的专栏 761

Android之关于电话录音原理,目前的方法还是只能录MIC

对于android来讲,没有API直接支持对电话进行双向录音。 虽然MediaRecorder里面可以设置recodeRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);来表示要对双向进行录音,但是这个功能实际是不可使用的,唯一能直接使用的还是VOICE_MIC。也就是对MIC进行录音。现在很多的所谓录音软件应该都是用的MI

码莎拉蒂 1万+

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

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

hnjzfwy的博客 480

Android NDK层录音DEMO,C/C++

基于Opensl接口实现的录音demo,代码经过测试验证,功能正常,使用循环BUFFER实现录音和写入文件功能。需要的朋友可以参考使用

电流模架构Bandgap设计与仿真

带隙基准作为集成电路中一个重要模块,被广泛应用在低压差线性稳压(LDO)、充电电池保护芯片和通信电路、射频收发器、flash存储器等多种模拟及数模混合集成电路中,并且是片上集成系统(SOC)芯片中不可或缺的部分,为整个芯片提供精确的电压参考点............

qq_50968231的博客 5万+

Android native音频:录制播放的实现以及低延迟音频方案

文章目录1. 前言2. 工程准备3. 低延迟音频原理及功能实现方案4. 使用OpenSL ES4.1 播放器实现4.2 录音器实现4.3 Echo实现5. 使用AAudio5.1 播放器实现5.2 录音器实现5.3 Echo实现6. 使用Oboe7. 功能配置 1. 前言 Android提供了很多的多媒体接口,通常在java层,我们常用的就是AudioTrack和MediaPlayer进行音频播放。MediaPlayer不光可以播放音频,也可以播放视频,并支持少部分的解码。 而由于音视频通常计算量都很大,所

zuguorui的专栏 4628

高功率半导体叠阵激光准直

摘要:研究针对高功率半导体激光器叠阵的光束整形问题,通过Zemax软件模拟5个巴条(间隔1.81mm)的LD阵列(总功率300W)。采用非序列模式建立模型,设置慢轴/快轴发散角分别为5°和19.5°,并配置包含19个发光点的巴条结构。通过S-TIH53材质的快轴准直镜(焦距0.6mm)和慢轴准直镜(焦距2.88mm)进行光束整形,有效降低了快慢轴发散角。仿真结果表明该方法能显著改善叠阵激光的光束质量,为高亮度光纤耦合提供了解决方案。

Zhaxibamao的博客 915

Android下音频的测试程序tinyalsa(录音,放音,查看声卡信息)

audio代码比较复杂,除了音频参数,我们平时客制化的地方不多。所以没有太深入了解。建议先抽空看看如下代码:kernel, linux alsa 架构:kernel-3.10/sound/soc/mediatek/kernel-3.10/Documentation/sound/alsa/soc/android 上层alsa接口external/tinya...

weixin_34332905的博客 2831

新四化浪潮下,中年整车诊断工程师的黄金突围路

摘要 本文探讨了汽车电子诊断工程师在当前行业变革中的独特优势与发展路径。作者作为从业者指出,整车诊断岗位在汽车"新四化"浪潮中呈现"逆生长"特性:技术壁垒高、经验复利强、市场需求持续增长。中年工程师凭借丰富的案例积累和实战经验成为行业稀缺人才,薪资水平显著高于同龄段其他技术岗位。文章分析了四条职业发展路径(技术专家、技术管理、细分深耕、横向拓展),并警示了三大职业风险(执行层局限、技术停滞、成果沉淀不足)。最后提出具体建议,强调掌握UDS高阶、DoIP以太网诊断等核心技术的重要性,为中年技术人提供了清晰的职

Soly_kun的博客 309

【ARM Coresight | AMBA BUS | Cache 与 MMU/MPU | CoreLink | GCC | CSH 专栏导读】

1. ARM Coresight(SoC-400/SoC-600) 专栏 2. ARMv8/v9 CPU/Cache 专栏 3. Linux ARM AMBA 总线 专栏 4. Trace32/DS-5 专栏。。。

CodingCos的博客 8241

Android 随笔集 By tuliyuan

1.反射机制调用系统服务EthernetManager private void getNetInfo(Context context) { try { // Log.d(TAG, "dddd getNetInfo is " ); Method getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", new Cla

qq_18906227的博客 2281

Cadence PSpice 仿真11:电感型低通滤波器最坏情况分析仿真实战图文教程

在进行蒙特卡罗分析时会将元器件的容差考虑进去,然后通过运行多次的随机仿真,得到电路输出的一个范围。但是多数情况下设计师关注的只是电路的最坏情况是否满足设计要求。PSpice的最坏情况分析是取在容差的边界点元件参数进行仿真以计算出电路输出的最坏情况。所以如果如果电路中有10个元件的话,需要先取10个元件的标准值仿真1次,然后再分别在10个元件的容差边界处各仿真1次,然后再对仿真结果进行统计和计算,再最坏情况下再仿真1次。

fydar的博客 1944

腾讯混元MT1.5:1GB内存跑通端侧12语种翻译

端侧大模型推理正从‘云端依赖’走向‘真离线可用’,其核心在于轻量化架构与内存精算的协同突破。Transformer模型通过KV Cache动态复用、混合精度Embedding和计算-存储流水线调度,可在有限内存中维持高精度翻译能力;INT4量化已非简单位宽压缩,而是基于通道敏感度的分组感知策略(GCAQ),兼顾体积压缩与BLEU稳定性。这类技术使中低端手机具备实时多语种翻译能力,广泛应用于无网环境、低功耗设备及隐私敏感场景。腾讯混元MT1.5正是这一范式的典型落地——1GB内存约束下支持中英日韩等12语种端

weixin_30764771的博客 477

2024年03月CCF-GESP编程能力等级认证Python编程五级真题解析

下面流程图在yr输入2024时,可以判定yr代表闰年,并输出 2月是29天 ,则图中菱形框中应该填入( )。 A. (yr % 400 == 0) or (yr % 4 == 0) B. ( yr % 400 == 0) or (yr % 4 == 0 and yr %100 != 0) C. ( yr % 400 == 0) and ( yr % 4 == 0) D. (yr % 400 == 0) and (yr % 4 == 0 and yr % 100 != 0)

人生就是一个不断学习的过程 746

AI故障预测系统架构设计:5个核心模块+3种主流架构,收藏这篇就够了

深夜11点,某汽车工厂的生产线突然停机——核心机器人的轴承磨损过度导致卡壳。维修人员连夜抢修,直接损失超过50万元,还延误了客户的交货期。传统的“事后维修”或“定期维护”要么代价高昂,要么防不胜防。AI故障预测系统的出现,让“精准维护”成为可能——它能通过传感器数据提前7-30天预警故障,将停机损失降低80%以上。知道要做故障预测,却不知道怎么搭系统——数据怎么采集?特征怎么提取?模型怎么部署?架构怎么选?本文将解决这些问题:我们会拆解AI故障预测系统的5个核心模块,分析3种主流架构。

AIGC应用创新大全的博客 1020

Gemma 4:面向边缘设备的高智能密度AI模型实战指南

大语言模型正从云端向终端下沉,‘边缘AI’成为开发者落地关键路径。理解模型的智能密度——即单位硬件资源(内存带宽、功耗、显存)所能稳定输出的有效推理能力——是选型核心。Gemma 4并非参数堆砌,而是通过软硬协同设计(如KV缓存优化、算子融合、量化感知训练)实现低延迟、低功耗、高鲁棒性的端侧运行。它原生支持多模态输入与Apache 2.0开源许可,兼顾法律合规与部署自由,适用于Android App、工业质检、医疗离线系统等真实场景。E2B/E4B等规格差异本质是面向不同边缘硬件的战术分工,而非简单大小之分

weixin_34151004的博客 441

Opus Clip AI技术浅析(一)

Opus Clip AI 是一款由Opus公司推出的AI视频剪辑工具,旨在通过人工智能技术简化视频编辑过程,帮助用户快速从长视频中提取精彩片段,并生成适合社交媒体分享的短视频。

m0_75253143的博客 2152

Gemma 4实战指南:边缘可驻留、推理可预测、微调可复现的AI系统

大语言模型(LLM)正从云端中心化走向设备端原生智能,核心挑战在于如何在算力受限、功耗敏感、部署环境多变的边缘设备上实现稳定高效的AI推理。‘边缘AI’不再仅指模型轻量化,而是要求模型架构与硬件物理约束深度协同——包括缓存容量、DSP指令集、内存带宽与散热边界。Gemma 4提出的‘边缘可驻留、推理可预测、微调可复现’三大原则,正是对这一趋势的技术回应:它通过硬件锚定设计(如E2B适配骁龙L3缓存)、智能密度量化(准确率/毫秒)、统一多模态协议栈等机制,将AI能力真正嵌入树莓派、Jetson、Android

cunbei2644的博客 1111

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

WebRTC回声消除算法在Android平台上的应用

WebRTC回声消除算法在Android平台上的应用 WebRTC的回声消除算法包括AEC、AECM(即AEC Mobile)。使用AEC时,可以启用估计延时不确定性(Delay Agnostic)的DA-AEC算法。 在Android设备上,我尝试了DA-AEC。 DA-AEC正确工作时,回声消除效果很明显,但不正确工作时,就有回声消除不掉的情况了。 在查找过程中,发现了chrome bug...

weixin_43744446的博客 3014

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Android 平台语音通话及回音消除、噪音消除研究(转)

一 Android操作系统由来 Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平 板电脑,由Google公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用“安 卓”或“安致”。Android操作系统最初由Andy Rubin开发,主要支持手机。2005年8月由Google收购注资。 2007年11月,Google与84家硬件制造商、软件开发商及电信营运商组建开放手机联盟共同研发改良Androi d系统。随后Google以Apache开

feelinghappy的专栏 2485

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

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

android 源码分析打开mic

1、demo   AudioManager  audioManager  =  (AudioManager)getSystemService("audio");   audioManager.setMode(AudioManager.STREAM_MUSIC);//设置声音模式   audioManager.setMicrophoneMute(true); //打开麦克风   权限:&lt;u...

liangtianmeng的专栏 2223

linphone 回声消除

声学回声消除是通过消除或者移除本地话筒中拾取到的远端的音频信号来阻止远端的声音返回去的一种处理方 法。linphone上使用speex 库实现了回声消除插件,speex库是目前开源的声学回声消除做的比较好的库。下 面总结一下,linphone上的回声消除部分。   第一部分--配置 linphone的声音部分,是可以配置的,初始化linphone的时候,会根据配置文件的内容来配置声音部分

好记性不如写博客! 1万+

Android音频API介绍「转载」

Player:Recorder:更多API细节可以参考官方提供的《OpenSL_ES_Specification_1.0.1.pdf》。播放逻辑可参考oarplayer中的音频输出代码。本篇介绍了Android提供的音频采集/播放接口,Java层可以使用AudioRecorder/AudioTrack,jni层推荐使用Oboe。不论哪个API都是最终通过framework层、HAL层与硬件打交道。音频采集时在应用层会有一个“AudioRecord”的线程来与framework的服务交互。

jinauto的博客 2917

Android Service组件实现后台音乐播放

Android Service以及Activity学习时实现的一个简单的后台音乐播放器。

陈Sir的博客 1万+

安卓手机麦克风测试应用开发

之前接了一个开发测试手机麦克风寿命apk的任务,主要要求是:1、可以控制手机麦克风的开启与关闭;2、麦克风接收的声音从听筒发出;3、可设置测试时间。下面我对该应用的开发做一相关讲解并附上代码。         开发难点:1、在话筒与听筒均开启的情况下,进入话筒的声音无法直接从听筒发出,需要做相关转换输出处理;                             2、时间任务的启动与停止;

best2012power的博客 1万+
上一篇: Android WebView设置cookie
下一篇: Python与设计模式(一)——Abstract Factory
hunter800421
博客等级 码龄22年 60粉丝 97原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值