最近在做UAC的項目,大概就是接收內核UAC的事件,也就是聲音相關事件。然后就是pcm_read和AudioTrackr->write之間互傳。感覺略微有點奇怪,所以簡單總結一下。
1 UAC的簡要流程
open_netlink_socket 打開內核窗口,類似于ioctl。
recvfrom 接收數據。
UAC_CAP_START 處理開始播放事件。
?? ?host_to_device
?? ??? ?tracker_data_thread 播放線程。
?? ??? ??? ?pcm_read->(AudioTrackr->write)
?? ??? ?pcm_open
?? ??? ?pcm_read
?? ??? ?pcm_close
?? ??? ?
UAC_CAP_STOP?處理停止播放事件。
?? ?
UAC_PLAY_START?處理開始錄音事件。
?? ?device_to_host
?? ??? ?recorder_data_thread
?? ??? ??? ?(AudioRecord->read)<-pcm_write
?? ??? ?pcm_open
?? ??? ?pcm_write
?? ??? ?pcm_close
?? ?
UAC_PLAY_STOP?處理停止錄音事件。
2 安卓音頻系統
https://source.android.com/docs/core/audio?hl=zh-cn
關于UAC的內容,居然也有說:
https://source.android.com/docs/core/audio/usb?hl=zh-cn
不過下面這兩個圖我覺得直觀一丟丟。
下面這個都包漿了。。。
大致就是幾層:
1 Java App層,這一層封裝最完善,但是只有最常規的操作,給開發app的帥哥做傻瓜式操作的。使用android.media.MediaPlayer。
2 Framework層,這一層可以使用AudioTracker和AudioRecorder,這一層接口比較底層一點,提供的功能比較多。可以實現實時處理和一些特效。Java和C++都可以用。下面還有個AudioFlinger,是用來做混音的。也是上下層的分隔。所以繞過Framework層,直接用HAL的接口,可能就有問題。
3 HAL接口。有HIDL和AIDL的,這一層理論上可以用,但是貌似比較少,起碼我們公司的大神都不在這層搞事。
4 ALSA接口,這一層是標準Linux的,花樣也是非常多。
3 App接口
沒啥好說的,這部分我也不是太熟悉,直接懟media.MediaPlayer即可。代碼說明一切吧。
package com.example.audioplayer;import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private MediaPlayer mediaPlayer;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button playButton = findViewById(R.id.play_button);Button stopButton = findViewById(R.id.stop_button);// 播放本地音頻文件mediaPlayer = MediaPlayer.create(this, R.raw.example_audio);// 如果你想播放網絡音頻流,可以使用下面的代碼// mediaPlayer = new MediaPlayer();// try {// mediaPlayer.setDataSource("http://your-audio-url.com/audio.mp3");// mediaPlayer.prepare(); // 同步準備,可能會阻塞主線程,建議使用異步準備// } catch (IOException e) {// e.printStackTrace();// }playButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mediaPlayer != null && !mediaPlayer.isPlaying()) {mediaPlayer.start();}}});stopButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mediaPlayer != null && mediaPlayer.isPlaying()) {mediaPlayer.stop();// 重新準備MediaPlayermediaPlayer.prepareAsync();}}});}@Overrideprotected void onDestroy() {super.onDestroy();if (mediaPlayer != null) {mediaPlayer.release();mediaPlayer = null;}}
}
4 AudioTracker和AudioRecorder
我這次項目用的就是這兩個,其實還是挺簡單,看個例子就夠了。。。
#include <android/media/AudioTrack.h>// 假設audioBuffer是一個已經加載好的音頻數據的short數組
short audioBuffer[]; // 音頻數據填充到這個數組中
int bufferSize = audioTrack->frameCount() * audioTrack->channelCount(); // 計算緩沖區大小// 創建一個AudioTrack實例
auto audioTrack = new android::media::AudioTrack(android::media::AudioTrack::STREAM_MUSIC, // 音頻流類型44100, // 采樣率44.1kHzandroid::media::AudioTrack::CHANNEL_OUT_STEREO, // 立體聲輸出android::media::AudioTrack::TRANSFER_MODE_STATIC, // 靜態模式bufferSize, // 緩沖區大小android::media::AudioTrack::MODE_STATIC // 靜態播放模式
);// 開始播放音頻
audioTrack->start();// 寫入數據到AudioTrack緩沖區
audioTrack->write(audioBuffer, bufferSize);// 播放完畢,暫停并釋放資源
audioTrack->stop();
delete audioTrack;
5 HAL
這部分位于vendor,上面的是位于system,所以還是區別很大。如果要在vendor搞事情,還是要用這個部分。
定義是在這個地方:https://android.googlesource.com/platform/hardware/interfaces/+/refs/heads/master/audio/
但是比較疑惑的一點是單位有大神說直接調用Hal,會碰壞系統。。。存疑中。。。
用的話直接用hardware/audio.h就可以。
#include <jni.h>
#include <string>
#include <android/log.h>
#include <hardware/hardware.h>
#include <hardware/audio.h>#define LOG_TAG "NativeAudio"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)extern "C" JNIEXPORT void JNICALL
Java_com_example_audioplayer_MainActivity_nativeInitAudio(JNIEnv *env, jobject thiz) {LOGD("Initializing Audio HAL");hw_module_t *module = nullptr;hw_device_t *device = nullptr;// Load the audio hardware moduleif (hw_get_module(AUDIO_HARDWARE_MODULE_ID, (const hw_module_t **)&module) == 0) {LOGD("Audio module loaded");// Open the audio hardware deviceif (module->methods->open(module, AUDIO_HARDWARE_INTERFACE, &device) == 0) {LOGD("Audio device opened");audio_hw_device_t *audioDevice = (audio_hw_device_t *)device;if (audioDevice && audioDevice->init_check(audioDevice) == 0) {LOGD("Audio device initialized");// Set up and start playback using audio_stream_outaudio_stream_out_t *streamOut = nullptr;audioDevice->open_output_stream(audioDevice, 0, AUDIO_DEVICE_OUT_SPEAKER,AUDIO_OUTPUT_FLAG_NONE, nullptr, &streamOut, nullptr);if (streamOut) {LOGD("Audio stream out opened");// Simplified example to play a buffer (should use actual audio data)size_t bufferSize = streamOut->common.get_buffer_size(&streamOut->common);uint8_t *buffer = new uint8_t[bufferSize];memset(buffer, 0, bufferSize); // Fill buffer with silence or actual audio datastreamOut->write(streamOut, buffer, bufferSize);delete[] buffer;audioDevice->close_output_stream(audioDevice, streamOut);} else {LOGD("Failed to open audio stream out");}} else {LOGD("Audio device initialization failed");}device->close(device);} else {LOGD("Failed to open audio device");}} else {LOGD("Failed to load audio module");}
}
6 ALSA
這個部分有點略大,看看下次寫吧。。。還有一個OMX,以后有心情再寫吧。。。
最后回到一開始說的UAC,應該是新生成了音頻的節點,然后可以從這個節點讀取音頻數據,但是最后要將聲音從Android的接口放出去,所以那么搞。之前調試的時候,在UAC的模式下,好像也確實是生成了兩張聲卡。這部分感覺內容也挺多了,下次再總結。
參考:
https://source.android.com/docs/core/audio?hl=zh-cn
Android系統Audio框架介紹_android audio-CSDN博客
Android系統Audio框架介紹_android audio-CSDN博客