主頁 >  其他 > Android音頻API

Android音頻API

2021-09-17 11:45:43 其他

Android系統提供了四個層面的音頻API:

  1. Java層MediaRecorder&MediaPlayer系列;
  2. Java層AudioTrack&AudioRecorder系列;
  3. Jni層opensles;
  4. JNI層AAudio(Android O引入)

下面分別介紹這些API的使用及特點,

1. MediaRecorder&MediaPlayer

MediaRecorder與MediaPlayer并不能算完整意義的音頻API,它們只是系統音頻API的封裝,除了采集/播放,他們集成了編碼/解碼、復用/解復用等能力,它們在最底層還是呼叫了AudioRecorder、AudioTrack,下面主要介紹它們的幾個主要的配置項,

1.1 MediaRecorder

MediaRecorder因為已經集成了錄音、壓縮編碼、封裝復用等功能,所以使用起來相對比較簡單,

MediaRecorder 使用起來相對簡單,主要設定以下幾項:

  1. 音源setAudioSource,系統提供的選項:
    1. AudioSource.DEFAULT:默認音頻源;
    2. AudioSource.MIC:麥克風;
    3. AudioSource.VOICE_UPLINK:上行電話錄音,android.Manifest.permission#CAPTURE_AUDIO_OUTPUT;
    4. AudioSource.VOICE_DOWNLINK:下行電話錄音,android.Manifest.permission#CAPTURE_AUDIO_OUTPUT;
    5. AudioSource.VOICE_CALL:上下行電話錄音,android.Manifest.permission#CAPTURE_AUDIO_OUTPUT;
    6. AudioSource.CAMCORDER:設定錄音來源于同方向的相機麥克風相同,若相機無內置相機或無法識別,則使用預設的麥克風
    7. AudioSource.VOICE_RECOGNITION:用于語音識別;
    8. AudioSource.VOICE_COMMUNICATION:用于語音通話;
    9. AudioSource.UNPRECESSED:原始音頻;
    10. AudioSource.VOICE_PERFORMANCE:低延遲用于滿足實時音頻處理;
    11. AudioSource.REMOTE_SUBMIX:用于傳輸系統混音的音頻流到遠端, android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
    12. AudioSource.ECHO_REFERENCE:回聲抑制參考信號,SystemApi,android.Manifest.permission.CAPTURE_AUDIO_OUTPUT;
    13. AudioSource.RADIO_TUNER:電臺廣播聲音,SystemApi;
    14. AudioSource.HOTWORD:搶占式的熱詞檢測,SystemApi,
  2. 編碼器setAudioEncoder
    1. Audio.Encoder.DEFAULT:
    2. Audio.Encoder.AMR_NB:
    3. Audio.Encoder.AMR_WB:
    4. Audio.Encoder.AAC:
    5. Audio.Encoder.HE_AAC:
    6. Audio.Encoder.AAC_ELD:4.1+
    7. Audio.Encoder.VORBIS:
    8. Audio.Encoder.OPUS:Android 10+
  3. 復用器setOutputFormat
    1. OutputFormat.DEFAULT
    2. OutputFormat.THREE_GPP:3GP;
    3. OutputFormat.MPEG_4:mp4;
    4. OutputFormat.RAW_AMR:.aac or .amr;
    5. OutputFormat.AMR_NB:amr nb;
    6. OutputFormat.AMR_WB:amr wb;
    7. OutputFormat.AAC_ADIF:aac adif;
    8. OutputFormat.AAC_ADTS:aac adts;
    9. OutputFormat.OUTPUT_FORMAT_RTP_AVP:網路流;
    10. OutputFormat.MPEG_2_TS:ts封裝;
    11. OutputFormat.WEBM:webm 容器;
    12. OutputFormat.HEIF: heif容器;
    13. OutputFormat.OGG:ogg容器,
  4. 輸出檔案路徑setOutputFile

示例代碼:

MediaRecorder recorder = new MediaRecorder();
 recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
 recorder.setOutputFile(PATH_NAME);
 recorder.prepare();
 recorder.start();   // Recording is now started

 ...
 recorder.stop();
 recorder.reset();   // You can reuse the object by going back to setAudioSource() step
 recorder.release(); // Now the object cannot be reused

上面代碼只是基本使用方式,具體使用還需結合專案具體需求制定具體邏輯,但是MediaRecorder使用時需實體化,所以在不用時一定要記得即時釋放,以免造成記憶體泄漏,

MediaRecorder狀態圖:

總結:MediaRecorder 實作錄音比較簡單的,代碼量相對較少,較為簡明,但也有不足之處,例如輸出檔案格式選擇較少,錄音程序不能暫停等,

1.2 MediaPlayer

MediaPlayer使用示例:

MediaPlayer mediaPlayer = new MediaPlayer();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
      AudioAttributes audioAttributes = new AudioAttributes.Builder()
          .setUsage(AudioAttributes.USAGE_ALARM)
          .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
          .build();
      mediaPlayer.setAudioAttributes(audioAttributes);
    } else {
      mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
    }
mediaPlayer.reset();
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
          @Override
          public void onCompletion(MediaPlayer mp) {
            // 播放完成
          }
        });
mediaPlayer.setDataSource(path);
mediaPlayer.prepare();
mediaPlayer.start();
//or mediaPlayer.prepareAsync()并在setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)設定的回呼中start
1.2.1 狀態圖:

1.2.2setAudioStreamType提供的選項:
  1. AudioSystem.STREAM_VOICE_CALL:電話通話;
  2. AudioSystem.STREAM_SYSTEM:系統聲音;
  3. AudioSystem.STREAM_RING:電話響鈴聲;
  4. AudioSystem.STREAM_MUSIC:音樂播放;
  5. AudioSystem.STREAM_ALARM:鬧鐘;
  6. AudioSystem.STREAM_NOTIFICATION:通知

AudioAttributes用來替代stream types,它可以設定比stream types更多的屬性,有三個方面:

  1. usage(why):為什么要播放這個聲音?
  2. content type(what):播放的內容是什么?是可選項,有些usage,比如CONTENT_TYPE_MOVIE是movie usage,
  3. flags(how):如何影響播放,
1.2.3 setDataSource說明

setDataSource設定播放源,可以是本地檔案也可以是網路檔案,如果是網路檔案和本地比較大的檔案,在主執行緒中呼叫prepare可能會導致ANR,所以盡量用prepareAsync,然后再onPrepared回呼中呼叫start,

2. AudioRecorder&AudioTrack

2.1 AudioRecorder

AndioRecord 類的主要功能是讓各種 Java 應用能夠管理音瞥澩,以便它們通過此類能夠錄制平臺的聲音輸入硬體所收集的聲音,它的實作就是通過 “pulling 同步”(reading讀取)AudioRecord 物件的聲音資料來完成的,在錄音程序中,應用所需要做的就是通過后面三個類方法中的一個去及時地獲取 AudioRecord 物件的錄音資料, AudioRecord 類提供的三個獲取聲音資料的方法分別是 read(byte[], int, int), read(short[], int, int), read(ByteBuffer, int),無論選擇使用哪一個方法都必須事先設定方便用戶的聲音資料的存盤格式,

開始錄音的時候,一個 AudioRecord 需要初始化一個相關聯的聲音buffer,這個 buffer 主要是用來保存新的聲音資料,這個 buffer 的大小,我們可以在物件構造期間去指定,它表明一個 AudioRecord 物件還沒有被讀取(同步)聲音資料前能錄多長的音(即一次可以錄制的聲音容量),聲音資料從音頻硬體中被讀出,資料大小不超過整個錄音資料的大小(可以分多次讀出),即每次讀取初始化 buffer 容量的資料,

采集作業很簡單,我們只需要構造一個AudioRecord物件,然后傳入各種不同配置的引數即可,一般情況下錄音實作的簡單流程如下:

  1. 音頻源:我們可以使用麥克風作為采集音頻的資料源,可選引數跟MediaRecorder一致,
  2. 采樣率:一秒鐘對聲音資料的采樣次數,采樣率越高,音質越好,
  3. 音頻通道:單聲道,雙聲道等,
  4. 音頻格式:一般選用PCM格式,即原始的音頻樣本,
  5. 緩沖區大小:音頻資料寫入緩沖區的總數,可以通過AudioRecord.getMinBufferSize獲取最小的緩沖區,(將音頻采集到緩沖區中然后再從緩沖區中讀取),

代碼實作如下:

public class AudioRecorder {
    //音頻輸入-麥克風
    private final static int AUDIO_INPUT = MediaRecorder.AudioSource.MIC;
    //采用頻率
    //44100是目前的標準,但是某些設備仍然支持22050,16000,11025
    //采樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個等級
    private final static int AUDIO_SAMPLE_RATE = 16000;
    //聲道 單聲道
    private final static int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
    //編碼
    private final static int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;

    public void createDefaultAudio() {
        // 獲得緩沖區位元組大小
        int bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE,
                AUDIO_CHANNEL, AUDIO_ENCODING);
        AudioRecord audioRecord = new AudioRecord(AUDIO_INPUT, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, bufferSizeInBytes);
        audioRecord.startRecording();

        new Thread(new Runnable() {
            @Override
            public void run() {
                readsize = audioRecord.read(audiodata, 0, bufferSizeInBytes);
            	  if (AudioRecord.ERROR_INVALID_OPERATION != readsize) {
                	//handle audio data
            		}
            }
        }).start();
    }
}

2.2 AudioTrack

AudioTrack是Java層管理和播放單個音瞥澩提供的介面,它用來播放PCM原始資料,通過使用write(byte[], int, int)、write(short[], int, int)和write(float[], int, int, int)方法之一寫入資料實作播放,AudioTrack實體可以在兩種模式下運行:靜態模式或流模式,

  • 在流模式下,應用程式使用write()方法之一將連續的資料流寫入AudioTrack,當資料從Java層傳輸到native層并排隊等待播放時,它們會阻塞并回傳,當播放音頻資料塊時,流式模式是最有用的,例如:因為聲音播放的時間太長,所以記憶體無法容納;由于音頻資料的特性(高采樣率,每次采樣的位元數…);在播放之前排隊的音頻時接識訓生成,

  • 靜態模式用于處理適合記憶體且需要以最小延遲播放的短聲音場景,因此,靜態模式將更適合UI和游戲聲音,并盡可能減少開銷,

創建AudioTrack物件時,初始化其關聯的音頻緩沖區,在構造程序中指定的這個緩沖區的大小決定了AudioTrack在耗盡資料之前可以播放多長時間,對于使用靜態模式的AudioTrack,此大小是它可以播放的聲音的最大大小,對于流模式,資料將以小于或等于總緩沖區大小的塊寫入音頻接收器,AudioTrack不是final的,因此允許使用子類,但不建議這樣使用,

靜態模式播放示例:

AudioAttributes audioAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
AudioFormat auidoFormat = new AudioFormat.Builder().setSampleRate(22050)
        .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
        .build();
AudioTrack audioTrack = new AudioTrack(audioAttributes,auidoFormat,audioData.length,AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE);
audioTrack.write(audioData, 0, audioData.length);
if(audioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
    Toast.makeText(this,"AudioTrack初始化失敗!",Toast.LENGTH_SHORT).show();
    return;
} 
audioTrack.play();

流模式播放示例:

// ************ 流播放 ************ 
final int minBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, AudioFormat.CHANNEL_OUT_MONO, AUDIO_FORMAT);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA)
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
        .build();
AudioFormat auidoFormat = new AudioFormat.Builder().setSampleRate(22050)
        .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
        .build();
audioTrack = new AudioTrack(audioAttributes,auidoFormat,minBufferSize,AudioTrack.MODE_STREAM,AudioManager.AUDIO_SESSION_ID_GENERATE);
// 檢查初始化是否成功
if(audioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
    Toast.makeText(this,"AudioTrack初始化失敗!",Toast.LENGTH_SHORT).show();
    return;
} 
// 播放
audioTrack.play();
//子執行緒中檔案流寫入
workHandler.post(new Runnable() {
   @Override
   public void run() {
       try {
           final File file = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC), "test.pcm");
           FileInputStream fileInputStream = new FileInputStream(file);
           byte[] tempBuffer = new byte[minBufferSize];
           while (fileInputStream.available() > 0) {
               int readCount = fileInputStream.read(tempBuffer);
               if (readCount == AudioTrack.ERROR_INVALID_OPERATION ||
                       readCount == AudioTrack.ERROR_BAD_VALUE) {
                   continue;
               }
               if (readCount != 0 && readCount != -1) {
                   audioTrack.write(tempBuffer, 0, readCount);
               }
           }
           fileInputStream.close();
       } catch (IOException ioe) {
           ioe.printStackTrace();
       }
   }
});

3. OpenSL ES

OpenSL ES (Open Sound Library for Embedded Systems)是無授權費、跨平臺、針對嵌入式系統精心優化的硬體音頻加速API,它為嵌入式移動多媒體設備上的本地應用程式開發者提供標準化, 高性能,低回應時間的音頻功能實作方法,并實作軟/硬體音頻性能的直接跨平臺部署,降低執行難度,促進高級音頻市場的發展,簡單來說OpenSL ES是一個嵌入式跨平臺免費的音頻處理庫, iOS未暴露OpenSL ES介面,Android的OpenSL ES庫是在NDK的platforms檔案夾對應android平臺先相應cpu型別里面:

$ ls ~/Library/Android/ndk/ndk-bundle/android-ndk-r17c/platforms/android-28/arch-arm/usr/lib/
crtbegin_dynamic.o      libGLESv3.so            libcompiler_rt-extras.a libnativewindow.so
crtbegin_so.o           libOpenMAXAL.so         libdl.a                 libneuralnetworks.so
crtbegin_static.o       libOpenSLES.so          libdl.so                libstdc++.a
crtend_android.o        libaaudio.so            libjnigraphics.so       libstdc++.so
crtend_so.o             libandroid.so           liblog.so               libsync.so
libEGL.so               libc.a                  libm.a                  libvulkan.so
libGLESv1_CM.so         libc.so                 libm.so                 libz.a
libGLESv2.so            libcamera2ndk.so        libmediandk.so          libz.so

Android 2.3 (API 9) 即開始支持 OpenSL ES 標準了,通過 NDK 提供相應的 API 開發介面(參考:source.android.com/devices/aud… 實作的 OpenSL ES 只是 OpenSL 1.0.1 的子集,并且進行了擴展,因此,對于 OpenSL ES API 的使用,我們還需要特別留意哪些是 Android 支持的,哪些是不支持的,具體相關檔案的地址位于 NDK docs 目錄下:

NDKroot/docs/Additional_library_docs/opensles/index.html
NDKroot/docs/Additional_library_docs/opensles/OpenSL_ES_Specification_1.0.1.pdf

OpenSL ES 有兩個必須理解的概念,就是 Object 和 Interface,Object 可以想象成 Java 的 Object 類,Interface 可以想象成 Java 的 Interface,但它們并不完全相同,其實就是為了用面向程序的語言實作面向物件的介面,下面進一步解釋他們的關系: (1) 每個 Object 可能會存在一個或者多個 Interface,官方為每一種 Object 都定義了一系列的 Interface (2)每個 Object 物件都提供了一些最基礎的操作,比如:Realize,Resume,GetState,Destroy 等等,如果希望使用該物件支持的功能函式,則必須通過其 GetInterface 函式拿到 Interface 介面,然后通過 Interface 來訪問功能函式 (3)并不是每個系統上都實作了 OpenSL ES 為 Object 定義的所有 Interface,所以在獲取 Interface 的時候需要做一些選擇和判斷

3.1 常用的物件和結構體

在 OpenSL ES 中,一切 API 的訪問和控制都是通過 Interface 來完成的,連 OpenSL ES 里面的 Object 也是通過 SLObjectItf Interface 來訪問和使用的,

3.1.1 Engine Object 和 SLEngineItf Interface

OpenSL ES 里面最核心的物件就是:Engine Object,音頻引擎物件,它主要提供如下兩個功能:

(1)管理 Audio Engine 的生命周期

(2)提供管理介面: SLEngineItf,該介面可以用來創建所有其他的 Object 物件

(3)提供設備屬性查詢介面:SLEngineCapabilitiesItf 和 SLAudioIODeviceCapabilitiesItf,這些介面可以查詢設備的一些屬性資訊

Engine Object 物件的創建方法如下:

SLObjectItf engineObject;
slCreateEngine( &engineObject, 0, nullptr, 0, nullptr, nullptr );1.2.

初始化/銷毀:

(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);(*engineObject)->Destroy(engineObject);1.2.

獲取管理介面:

SLEngineItf engineEngine;(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &(engineEngine));1.2.

SLObjectItf結構定義:

struct SLObjectItf_ {    SLresult (*Realize) (SLObjectItf self,SLboolean async);    SLresult (*Resume) (SLObjectItf self,SLboolean async);    SLresult (*GetState) (SLObjectItf self,SLuint32 * pState);    SLresult (*GetInterface) (SLObjectItf self, const SLInterfaceID iid, void * pInterface);    SLresult (*RegisterCallback) (SLObjectItf self, slObjectCallback callback, void * pContext);    void (*AbortAsyncOperation) (SLObjectItf self);    void (*Destroy) (SLObjectItf self);    SLresult (*SetPriority) (SLObjectItf self, SLint32 priority, SLboolean preemptable);    SLresult (*GetPriority) (SLObjectItf self, SLint32 *pPriority, SLboolean *pPreemptable);    SLresult (*SetLossOfControlInterfaces) (SLObjectItf self, SLint16 numInterfaces, SLInterfaceID * pInterfaceIDs, SLboolean enabled);};typedef const struct SLObjectItf_ * const * SLObjectItf;

這里的SLObjectItf是一個二級指標,它指向的是一個結構體指標,任何創建出來的Object都必須呼叫 Realize 方法做初始化,在不需要的時候可以使用 Destroy 方法來釋放資源,

下面我們就可以使用 engineEngine 來創建所有 OpenSL ES 的其他物件了,

3.1.2 Media Object

OpenSL ES 里面另一組比較重要的物件就是 Media Object ,代表著多媒體功能的抽象,比如:player、recorder 等等,

我們可以通過 SLEngineItf 提供的 CreateAudioPlayer 方法來創建一個 player 物件實體,可以通過 SLEngineItf 提供的 CreateAudioRecorder 方法來創建一個 recorder 實體,

3.1.3 Data Source 和 Data Sink

OpenSL ES 里面,這兩個結構體均是作為創建 Media Object 物件時的引數而存在的,data source 代表著輸入源的資訊,即資料從哪兒來、輸入的資料引數是怎樣的;而 data sink 則代表著輸出的資訊,即資料輸出到哪兒、以什么樣的引數來輸出,

Data Source 的定義如下:

typedef struct SLDataSource_ {      void *pLocator;      void *pFormat;} SLDataSource;1.2.3.4.

Data Sink 的定義如下:

typedef struct SLDataSink_ {    void *pLocator;    void *pFormat;} SLDataSink;1.2.3.4.

其中,pLocator 主要有如下幾種:

SLDataLocator_AddressSLDataLocator_BufferQueueSLDataLocator_IODeviceSLDataLocator_MIDIBufferQueueSLDataLocator_URI1.2.3.4.5.

也就是說,Media Object 物件的輸入源/輸出源,既可以是 URL,也可以 Device,或者來自于緩沖區佇列等等,完全是由 Media Object 物件的具體型別和應用場景來配置,

3.2 狀態機制

OpenSL ES 還有一個比較重要的概念,就是它的狀態機制,如圖所示:

任何一個 OpenSL ES 的物件,創建成功后,都進入 SL_OBJECT_STATE_UNREALIZED 狀態,這種狀態下,系統不會為它分配任何資源,直到呼叫 Realize 函式為止,

Realize 后的物件,就會進入 SL_OBJECT_STATE_REALIZED 狀態,這是一種“可用”的狀態,只有在這種狀態下,物件的各個功能和資源才能正常地訪問,

當一些系統事件發生后,比如出現錯誤或者 Audio 設備被其他應用搶占,OpenSL ES 物件會進入 SL_OBJECT_STATE_SUSPENDED 狀態,如果希望恢復正常使用,需要呼叫 Resume 函式,

當呼叫物件的 Destroy 函式后,則會釋放資源,并回到 SL_OBJECT_STATE_UNREALIZED 狀態,

簡言之,一個 OpenSL ES 物件的生命周期,就是從 create 到 destroy 的程序,生命周期的控制,都是通過開發者顯示呼叫來完成的,

3.3 API呼叫流程總結

Player:

Recorder:

更多API細節可以參考官方提供的《OpenSL_ES_Specification_1.0.1.pdf》,播放邏輯可參考oarplayer中的 github.com/qingkouwei/… 音頻輸出代碼,

4. AAudio & Oboe

AAudio 是在 Android O 版本中引入的全新 Android C API,此 API 專為需要低延遲的高性能音頻應用而設計,應用通過讀取資料并將資料寫入流來與 AAudio 進行通信,

AAudio API 采用最精簡的設計,不執行以下功能:

  • 音頻設備列舉
  • 音頻端點之間的自動化路由
  • 檔案 IO
  • 解碼壓縮的音頻
  • 在單一回呼中自動呈交所有輸入/流,

每個流都連接到單個音頻設備,音頻設備是硬體介面或虛擬端點,用作連續的數字音頻資料流的來源或接收器,不要將音頻設備(內置麥克風或藍牙耳機)與運行應用的 Android 設備(手機或智能手表)混淆,可以使用 AudioManager 方法 getDevices() 來發現 Android 設備上可用的音頻設備,該方法會回傳每個設備 type 的相關資訊,Android 設備上的每個音頻設備都具有唯一 ID,可以使用該 ID 將音頻流與特定音頻設備系結,但是,在大多數情況下,可以讓 AAudio 選擇默認的主要設備,無需自己指定,接到流的音頻設備負責確定該流是用于輸入還是輸出,流只能在一個方向上移動資料,定義流時,您還可以設定其方向,打開流時,Android 會執行檢查,確保音頻設備與流方向一致,

4.1 API說明

4.1.1 AAudio創建音頻流流程

AAudio 庫使用了構建器設計模式,并提供 AAudioStreamBuilder,

  1. 創建 AAudioStreamBuilder:

    AAudioStreamBuilder *builder;aaudio_result_t result = AAudio_createStreamBuilder(&builder);
    
  2. 使用與流引數對應的構建器函式,在構建器中設定音頻流配置,可供使用的可選設定函式如下所示:

    AAudioStreamBuilder_setDeviceId(builder, deviceId);AAudioStreamBuilder_setDirection(builder, direction);AAudioStreamBuilder_setSharingMode(builder, mode);AAudioStreamBuilder_setSampleRate(builder, sampleRate);AAudioStreamBuilder_setChannelCount(builder, channelCount);AAudioStreamBuilder_setFormat(builder, format);AAudioStreamBuilder_setBufferCapacityInFrames(builder, frames);
    

    注意:這些方法不會報告諸如常量未定義或值超出范圍之類的錯誤,

    如果未指定 deviceId,將默認為主要輸出設備,如果您未指定流方向,將默認為輸出流,對于所有其他引數,可明確設定值,也可以完全不指定引數或將其設定為 AAUDIO_UNSPECIFIED,讓系統分配最佳值,為安全起見,在創建音頻流之后,需要檢查其狀態,

  3. 配置 AAudioStreamBuilder 之后,用其創建流:

    AAudioStream *stream;result = AAudioStreamBuilder_openStream(builder, &stream);
    
  4. 創建流之后,驗證其配置,如果已指定采樣格式、采樣率或每幀樣本數,這些設定不會變更,如果已指定共享模式或緩沖區容量,這些設定可能會變更,具體取決于流的音頻設備能力,以及運行該流的 Android 設備,作為一種良好的防御性編程習慣,您應該先檢查流的配置,然后再使用,您可使用相應函式檢索與每項構建器設定對應的流設定:

    AAudioStreamBuilder_setDeviceId()AAudioStream_getDeviceId()
    AAudioStreamBuilder_setDirection()AAudioStream_getDirection()
    AAudioStreamBuilder_setSharingMode()AAudioStream_getSharingMode()
    AAudioStreamBuilder_setSampleRate()AAudioStream_getSampleRate()
    AAudioStreamBuilder_setChannelCount()AAudioStream_getChannelCount()
    AAudioStreamBuilder_setFormat()AAudioStream_getFormat()
    AAudioStreamBuilder_setBufferCapacityInFrames()AAudioStream_getBufferCapacityInFrames()
  5. 可以保存該構建器,以便將來再用其創建更多流,但是,如果不打算再使用該構建器,則應將其洗掉,

    AAudioStreamBuilder_delete(builder);
    
4.1.2 AAudio使用音頻流

僅當流處于“已開始”狀態時,資料才會通過流來流動,,使用以下函式請求狀態轉換:

aaudio_result_t result;result = AAudioStream_requestStart(stream);result = AAudioStream_requestStop(stream);result = AAudioStream_requestPause(stream);result = AAudioStream_requestFlush(stream);
4.1.3 AAudio讀取和寫入音頻流

在流啟動后,可通過兩種方法來處理流中的資料:

  • 使用高優先級回呼,
  • 使用函式 AAudioStream_read(stream, buffer, numFrames, timeoutNanos)AAudioStream_write(stream, buffer, numFrames, timeoutNanos) 來讀取或寫入流,

對于傳輸指定幀數的阻塞讀取或寫入操作,請將 timeoutNanos 設定為大于零, 對于非阻塞呼叫,請將 timeoutNanos 設定為零,在這種情況下,結果將是傳輸的實際幀數,

讀取輸入值時,您應驗證是否已讀取正確數量的幀,如果未讀取正確數量的幀,緩沖區可能包含未知的資料,從而引起音頻干擾,您可以在緩沖區中填入零,以產生靜音效果:

aaudio_result_t result =    AAudioStream_read(stream, audioData, numFrames, timeout);if (result < 0) {  // Error!}if (result != numFrames) {  // pad the buffer with zeros  memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,      sizeof(sample_type) * (numFrames - result) * samplesPerFrame);}
4.1.4 AAudio關閉音頻流

使用完流之后,請將其關閉:

AAudioStream_close(stream);

關閉流之后,便無法將其與任何基于流的 AAudio 函式配合使用,

4.1.5 AAudio斷開連接的音頻流

如果發生以下任一事件,音頻流隨時可能會斷開連接:

  • 關聯的音頻設備不再處于連接狀態(例如,在拔出頭戴式耳機時),
  • 發生內部錯誤,
  • 某音頻設備不再是主要音頻設備,

流斷開連接時,其狀態為“已斷開連接”,所有嘗試執行 AAudioStream_write 或其他函式的操作都會回傳錯誤,無論是什么錯誤代碼,您始終必須停止并關閉已斷開連接的流,

如果您使用的是資料回呼(而不是某個直接讀取/寫入方法),那么當流斷開連接時,您不會收到任何回傳代碼,如需在發生這種情況時接收通知,請撰寫 AAudioStream_errorCallback 函式,然后使用 AAudioStreamBuilder_setErrorCallback() 注冊該函式,

果您在錯誤回呼執行緒中收到連接已斷開的通知,則流的停止和關閉必須從其他執行緒中完成,否則可能出現死鎖,

請注意,如果打開新的流,其設定可能與原始流不同(例如,framesPerBurst):

void errorCallback(AAudioStream *stream,                   void *userData,                   aaudio_result_t error) {    // Launch a new thread to handle the disconnect.    std::thread myThread(my_error_thread_proc, stream, userData);    myThread.detach(); // Don't wait for the thread to finish.}

4.2 狀態轉換

AAudio 流一般有以下五種穩定狀態(本部分結尾將介紹錯誤狀態 Disconnected):

  • 打開
  • 已開始
  • 已暫停
  • 已重繪
  • 已停止

狀態切換函式是異步函式,因此狀態不會立即變更,當您請求變更狀態時,流會進入以下相應的過渡狀態:

  • 正在開始
  • 正在暫停
  • 正在重繪
  • 正在停止
  • 正在關閉

以下狀態圖將穩定狀態顯示為圓角矩形,而將過渡狀態顯示為虛線矩形,盡管未顯示,但您可從任意狀態呼叫 close()

AAudio 未提供回呼函式來提醒您狀態發生變更,您可使用特殊函式 AAudioStream_waitForStateChange(stream, inputState, nextState, timeout) 等待狀態變更,

此函式本身并不會檢測狀態變更情況,也不會等待特定的狀態,而是等待當前狀態偏離您指定的 inputState

例如,請求暫停后,流應立即進入“正在暫停”過渡狀態,并在稍后某個時刻進入“已暫停”狀態,但不保證一定如此,由于無法等待“已暫停”狀態,請使用 waitForStateChange() 來等待除“正在暫停”之外的任何狀態,方法如下:

aaudio_stream_state_t inputState = AAUDIO_STREAM_STATE_PAUSING;aaudio_stream_state_t nextState = AAUDIO_STREAM_STATE_UNINITIALIZED;int64_t timeoutNanos = 100 * AAUDIO_NANOS_PER_MILLISECOND;result = AAudioStream_requestPause(stream);result = AAudioStream_waitForStateChange(stream, inputState, &nextState, timeoutNanos);

如果流的狀態并非“正在暫停”(即 inputState,我們假定這就是當前執行呼叫時的狀態),該函式會立即回傳,否則,函式會阻止運行,直至狀態不再是“正在暫停”,或者超時,當函式回傳時,引數 nextState 會顯示流的當前狀態,

您可在呼叫開始、停止或重繪請求后使用這種方法,將相應的過渡狀態用作 inputState,不要在呼叫 AAudioStream_close() 之后呼叫 waitForStateChange(),因為流在關閉時會立即被洗掉,此外,不要在另一執行緒運行 waitForStateChange() 時呼叫 AAudioStream_close()

4.3 優化性能

我們可以通過調整內部緩沖區,使用特殊的高優先級執行緒,優化音頻應用的性能,

4.3.1 調整緩沖區以最大限度減少延遲時間

AAudio 會將資料傳入其維護的內部緩沖區,并從中傳出資料(每個音頻設備各有一個內部緩沖區),

注意:不要將 AAudio 的內部緩沖區與 AAudio 流讀取或寫入函式的緩沖區引數混淆,

緩沖區的容量是緩沖區中可以存放的資料總量,我們可以呼叫 AAudioStreamBuilder_setBufferCapacityInFrames() 來設定容量,這個方法將可分配的容量限制為設備允許的最大值,使用 AAudioStream_getBufferCapacityInFrames() 可以驗證緩沖區的實際容量,

應用不必使用緩沖區的全部容量,可以設定 AAudio 填充緩沖區空間的大小上限,緩沖區空間大小不得超過它的容量,而且通常小于其容量,可以通過控制緩沖區大小,確定填充緩沖區所需的脈沖串數,從而控制延遲時間,可以使用 AAudioStreamBuilder_setBufferSizeInFrames()AAudioStreamBuilder_getBufferSizeInFrames() 方法來處理緩沖區大小,

應用播放音頻時,會將資料寫入緩沖區并阻止運行,直至寫入完成,AAudio 以離散的脈沖串從緩沖區中讀取資料,每個脈沖串都包含多個音頻幀,而且通常小于所讀取的緩沖區大小,脈沖串的大小及速率由系統控制,而這些屬性通常由音頻設備的電路指定,雖然無法更改脈沖串的大小或速率,但可以根據內部緩沖區所含的脈沖串數量來設定內部緩沖區大小,通常,當 AAudioStream 的緩沖區大小是所報告脈沖串大小的倍數時,延遲時間最短,

優化緩沖區空間大小的一種方法是從較大的緩沖區開始,逐漸將其減小直至開始出現緩沖區不足現象,再稍稍將其調大,此外,也可以從較小的緩沖區空間大小開始,如果出現緩沖區不足現象,則增大緩沖區空間大小,直至輸出再次流暢為止,這個程序推進速度很快,很可能在用戶開始播放第一個音頻前就已完成,可以先以靜音執行初始緩沖區大小調整,確保用戶不會聽到任何音頻干擾聲,隨著時間推移,系統性能可能會有所變化(例如,用戶可能會關閉飛行模式),因為緩沖區調整所產生的開銷非常小,應用可以在對流讀取或寫入資料的同時,連續不斷地調整緩沖區,

以下是緩沖區優化回圈的示例:

int32_t previousUnderrunCount = 0;int32_t framesPerBurst = AAudioStream_getFramesPerBurst(stream);int32_t bufferSize = AAudioStream_getBufferSizeInFrames(stream);int32_t bufferCapacity = AAudioStream_getBufferCapacityInFrames(stream);while (go) {    result = writeSomeData();    if (result < 0) break;    // Are we getting underruns?    if (bufferSize < bufferCapacity) {        int32_t underrunCount = AAudioStream_getXRunCount(stream);        if (underrunCount > previousUnderrunCount) {            previousUnderrunCount = underrunCount;            // Try increasing the buffer size by one burst            bufferSize += framesPerBurst;            bufferSize = AAudioStream_setBufferSize(stream, bufferSize);        }    }}

對于輸入流來說,使用這種方法優化緩沖區大小并無益處,輸入流以盡可能快的速度運行,以嘗試將快取資料量保持在最低限度,然后在應用被搶占時填補緩沖區,

4.3.2 使用高優先級回呼

如果應用從原始執行緒中讀取或寫入音頻資料,可能會被搶占或遇到定時抖動,進而可能引起音頻干擾,使用較大的緩沖區有助于避免此類干擾,但是如果緩沖區較大,音頻延遲時間也會更長,對于要求延遲時間較短的應用,音頻流可以使用一個異步回呼函式,將資料傳輸到應用并從中傳輸資料,AAudio 會在優先級較高的執行緒中執行該回呼,這有助于改善性能,

該回呼函式的原型如下所示:

typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(        AAudioStream *stream,        void *userData,        void *audioData,        int32_t numFrames);

使用流構建方式來注冊回呼:

AAudioStreamBuilder_setDataCallback(builder, myCallback, myUserData);

在最簡單的情況下,流會定期執行該回呼函式,以獲取用于下一個脈沖串的資料,

該回呼函式不應對呼叫它的流執行讀取或寫入操作,如果該回呼屬于某個輸入流,那么您的代碼應處理在 audioData 緩沖區(指定為第三個引數)中提供的資料,如果該回呼屬于某個輸出流,那么您的代碼應將資料放入該緩沖區,

例如,可以使用回呼來連續生成正弦波輸出,如下所示:

aaudio_data_callback_result_t myCallback(        AAudioStream *stream,        void *userData,        void *audioData,        int32_t numFrames) {    int64_t timeout = 0;    // Write samples directly into the audioData array.    generateSineWave(static_cast<float *>(audioData), numFrames);    return AAUDIO_CALLABCK_RESULT_CONTINUE;}

使用 AAudio 可以處理多個流,可以將其中一個流用作主流,并在用戶資料中傳遞指向其他流的指標,針對主流注冊回呼,然后,對其他流使用非阻塞 I/O,以下是將輸入流傳遞到輸出流的往回傳呼示例,主呼叫流是輸出流,輸入流包括在用戶資料中,

該回呼從輸入流執行非阻塞讀取,以將資料放入輸出流的緩沖區:

aaudio_data_callback_result_t myCallback(        AAudioStream *stream,        void *userData,        void *audioData,        int32_t numFrames) {    AAudioStream *inputStream = (AAudioStream *) userData;    int64_t timeout = 0;    aaudio_result_t result =        AAudioStream_read(inputStream, audioData, numFrames, timeout);  if (result == numFrames)      return AAUDIO_CALLABCK_RESULT_CONTINUE;  if (result >= 0) {      memset(static_cast<sample_type*>(audioData) + result * samplesPerFrame, 0,          sizeof(sample_type) * (numFrames - result) * samplesPerFrame);      return AAUDIO_CALLBACK_RESULT_CONTINUE;  }  return AAUDIO_CALLBACK_RESULT_STOP;}

請注意,在此示例中,假定輸入流和輸出流的通道數量、格式和采樣率均相同,流的格式可以不匹配,只要代碼正確處理轉換即可,

4.3.3 設定性能模式

每個 AAudioStream 都具有性能模式,而這對應用行為的影響很大,共有三種模式:

  • AAUDIO_PERFORMANCE_MODE_NONE 是默認模式,這種模式使用在延遲時間與節能之間取得平衡的基本流,
  • AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 使用較小的緩沖區和經優化的資料路徑,以減少延遲時間,
  • AAUDIO_PERFORMANCE_MODE_POWER_SAVING 使用較大的內部緩沖區,以及以延遲時間為代價換取節能優勢的資料路徑,

可以通過呼叫 setPerformanceMode() 來選擇性能模式,并通過呼叫 getPerformanceMode() 來發現當前模式,

如果在應用中縮短延遲時間比節能更重要,請使用 AAUDIO_PERFORMANCE_MODE_LOW_LATENCY,這對互動性非常強的應用(例如游戲或鍵盤合成器)非常有用,

如果在您的應用中節能比縮短延遲時間更重要,請使用 AAUDIO_PERFORMANCE_MODE_POWER_SAVING,對于回放先前生成的音樂的應用(例如流式音頻或 MIDI 檔案播放器),情況通常如此,

在當前版本的 AAudio 中,為了盡量減少延遲時間,必須將 AAUDIO_PERFORMANCE_MODE_LOW_LATENCY 性能模式與高優先級回呼配合使用,請參閱以下示例:

// Create a stream builderAAudioStreamBuilder *streamBuilder;AAudio_createStreamBuilder(&streamBuilder);AAudioStreamBuilder_setDataCallback(streamBuilder, dataCallback, nullptr);AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);// Use it to create the streamAAudioStream *stream;AAudioStreamBuilder_openStream(streamBuilder, &stream);

官方檔案:developer.android.google.cn/ndk/guides/…

4.4 Oboe

谷歌開源了Oboe庫,Oboe 是一個 C++ 封裝容器,提供與 AAudio 非常相似的 API,它在 AAudio 可用時對其進行呼叫,并在 AAudio 不可用時回退使用 OpenSL ES,

以錄音為例實作程式:

class Callback:public oboe::AudioStreamCallback{DataCallbackResultOboeRecorder::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {    uint32_t size = 2 * numFrames * channels;    //handle audio data    return DataCallbackResult::Continue;}}oboe::AudioStreamBuilder builder;builder->setAudioApi(mAudioApi)      ->setFormat(mFormat)      ->setSharingMode(oboe::SharingMode::Exclusive)      ->setPerformanceMode(oboe::PerformanceMode::LowLatency)      ->setDeviceId(mRecordingDeviceId)      ->setFramesPerCallback(framesPerBuffer)      ->setDirection(oboe::Direction::Input)      ->setDataCallback(new Callback())      ->setSampleRate(samplerate)      ->setChannelCount(channels);oboe::AudioStream *mRecordingStream = nullptr;oboe::Result result = builder.openStream(&mRecordingStream);if (result == oboe::Result::OK && mRecordingStream) {      mRealSampleRate = mRecordingStream->getSampleRate();      mFormat = mRecordingStream->getFormat();      oboe::Result result = stream->requestStart();      mRecordingStream->waitForStateChange(StreamState::Starting, &state, 10*kNanosPerMillisecond);      if (result != oboe::Result::OK) {      	Logg("Error starting stream. %s", oboe::convertToText(result));      }} else {      Logg("Failed to create recording stream. Error: %s",oboe::convertToText(result));      result = mRecordingStream->close();}    

Oboe提供了很多創意的參考,可以直接在Oboe github倉庫查看,

5. 總結

本篇介紹了Android提供的音頻采集/播放介面,Java層可以使用AudioRecorder/AudioTrack,jni層推薦使用Oboe,不論哪個API都是最終通過framework層、HAL層與硬體打交道,音頻采集時在應用層會有一個“AudioRecord”的執行緒來與framework的服務互動,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/300830.html

標籤:其他

上一篇:如何制作其他尺寸的CIFAR10(附原始碼)

下一篇:A Unified Objective for Novel Class Discovery

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more