一:概述
这是一个c语言demo程序,android源码环境,编译得到 bin文件,push到设备上在shell环境运行,播放pcm数据。如果是app java开发,没有系统源码,就不建议往下看了。
之前有写过 使用 native层的TrackPlayer 、AudioFlinger 直接来播放pcm,说到底上面的TrackPlayer是封装的AudioFlinger的接口,而AudioFlinger是处理完软件层面的混音后,再AudioPolicy的指导下再将音频数据给到Audio 的HAL层。 这里,我们写一个c++ demo程序,源码上编译,直接调用音频的hal,写入pcm数据(当然如果是音频设备支持offlad,也是可以写入非pcm数据的。)
c++ main demo程序
Android 11 , aosp, 在 blueline pixle3 手机自己刷的aosp上进行了真机实验 ok
这个实际上就是对音频 hal库的使用demo,我们参考aosp中的ut(单元测试),源码中有不少ut,这里参考的是Android的 audio_remote_submix 这个音频hal的ut, audio_remote_submix是Android实现的一个软件层面的虚拟的音频hal设备,用于在wifidisplay时,将音频输出到这个虚拟的hal,就可以收集系统的音频数据,比如录屏就是用这个来录制系统输出的声音。 这个声音时经过了AudioFlinger软件层面混音后的数据。
源码位置:
hardware\libhardware\modules\audio_remote_submix
Android为这个hal库提供了UT测试,在:hardware\libhardware\modules\audio_remote_submix\tests\remote_submix_tests.cpp
这个ut 也比较简单:1. 打开r_submix 设备(其实就是加载对应的/vendor/lib64/hw/audio.r_submix.default.so 这个默认hal so库) 2. 打开流(这个address直接写的“1” 有待研究。) 3. 模拟写入一些数据。4. 关闭
本demo借鉴此ut, 打开primary 主音频(按道理也可以接上蓝牙(a2dp),往蓝牙上播放),然后从一个 48K,16bit, 双声道的pcm文件中循环读取pcm数据,写入,播放。
在audio.h文件中定义有这些设备 的名称:
// audio.h
// #define AUDIO_HARDWARE_MODULE_ID_PRIMARY "primary"
// #define AUDIO_HARDWARE_MODULE_ID_A2DP "a2dp"
// #define AUDIO_HARDWARE_MODULE_ID_USB "usb"
// #define AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX "r_submix"
// #define AUDIO_HARDWARE_MODULE_ID_CODEC_OFFLOAD "codec_offload"
// #define AUDIO_HARDWARE_MODULE_ID_STUB "stub"
// #define AUDIO_HARDWARE_MODULE_ID_HEARING_AID "hearing_aid"
// #define AUDIO_HARDWARE_MODULE_ID_MSD "msd"
11-08 10:19:49.762 5881 5881 E vndksupport: Could not load /vendor/lib64/hw/audio.primary.sdm845.so from sphal namespace: dlopen failed: library "libaudioutils.so" not found: needed by /vendor/lib64/hw/audio.primary.sdm845.so in namespace sphal.
11-08 10:19:49.763 5881 5881 E HAL : load: module=/vendor/lib64/hw/audio.primary.sdm845.so
10-30 14:42:02.629 8413 8413 E vndksupport: Could not load /vendor/lib64/hw/audio.r_submix.default.so from sphal namespace: dlopen failed: library "libmedia_helper.so" not found: needed by /vendor/lib64/hw/audio.r_submix.default.so in namespace sphal.

为简单起见,本demo直接利用源码中 audio_remote_submix /tests 的Android.bp 在该Android.bp中追加:
- //*******
-
- cc_binary {
- name: "AudioHalPlayer",
- srcs: ["AudioHalPlayer.cpp"],
-
- shared_libs: [
- "libhardware",
- "liblog",
- "libutils",
- ],
-
- cflags: ["-Wall", "-Werror", "-O0", "-g",],
-
- header_libs: ["libaudiohal_headers"],
- }
添加源码 cpp 文件:AudioHalPlayer.cpp
- // To run this test (as root):
- // 1) Build it
- // 2) adb push to /vendor/bin
- // 3) adb shell /vendor/bin/r_submix_tests
-
- #define LOG_TAG "AudioHalPlayer"
-
- #include
-
- #include
- #include
- #include
- #include
- #include
-
- using namespace android;
-
- static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev)
- {
- const hw_module_t *mod;
- int rc;
-
- rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
- if (rc)
- {
- printf("%s couldn't load audio hw module %s.%s (%s) \n", __func__,
- AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
- goto out;
- }
- rc = audio_hw_device_open(mod, dev);
- if (rc)
- {
- printf("%s couldn't open audio hw device in %s.%s (%s) \n", __func__,
- AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
- goto out;
- }
- if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN)
- {
- printf("%s wrong audio hw device version %04x \n", __func__, (*dev)->common.version);
- rc = BAD_VALUE;
- audio_hw_device_close(*dev);
- goto out;
- }
- return OK;
-
- out:
- *dev = NULL;
- return rc;
- }
- class AudioHalPlayer
- {
- public:
- void SetUp()
- {
- mDev = nullptr;
- // audio.h
- // #define AUDIO_HARDWARE_MODULE_ID_PRIMARY "primary"
- // #define AUDIO_HARDWARE_MODULE_ID_A2DP "a2dp"
- // #define AUDIO_HARDWARE_MODULE_ID_USB "usb"
- // #define AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX "r_submix"
- // #define AUDIO_HARDWARE_MODULE_ID_CODEC_OFFLOAD "codec_offload"
- // #define AUDIO_HARDWARE_MODULE_ID_STUB "stub"
- // #define AUDIO_HARDWARE_MODULE_ID_HEARING_AID "hearing_aid"
- // #define AUDIO_HARDWARE_MODULE_ID_MSD "msd"
- load_audio_interface(AUDIO_HARDWARE_MODULE_ID_PRIMARY, &mDev);
- printf("[%s%d] %d \n", __FUNCTION__, __LINE__, (mDev == nullptr));
- }
- void TearDown()
- {
- if (mDev != nullptr)
- {
- int status = audio_hw_device_close(mDev);
- mDev = nullptr;
- (void)status;
- }
- }
-
- void OpenOutputStream(
- const char *address, bool mono, uint32_t sampleRate, audio_stream_out_t **streamOut)
- {
- *streamOut = nullptr;
- struct audio_config configOut = {};
- configOut.channel_mask = mono ? AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO;
- configOut.sample_rate = sampleRate;
-
- if (mDev == nullptr)
- {
- printf("[%s%d] mDev==null\n", __FUNCTION__, __LINE__);
- return;
- }
- status_t result = mDev->open_output_stream(mDev,
- AUDIO_IO_HANDLE_NONE, AUDIO_DEVICE_NONE, AUDIO_OUTPUT_FLAG_NONE,
- &configOut, streamOut, address);
-
- (void)result;
- }
-
- audio_hw_device_t *mDev;
- };
-
- // 48k, 16bit(16/8 字节), 这里我们按照该每次写20ms的数据。( 1/50 秒), 双声道(*2)
- #define BUFFER_SIZE (48000 * 16 / 8 / 50 * 2)
- int main()
- {
- AudioHalPlayer mTest;
- // 1.0 // 加载so打开设备
- mTest.SetUp();
-
- // 2.0 // 打开流
- const char *address = "1";
- audio_stream_out_t *streamOut = nullptr;
- mTest.OpenOutputStream(address, false /*mono*/, 48000, &streamOut);
-
- if (streamOut == nullptr)
- {
- printf("[%s%d] streamOut==null\n", __FUNCTION__, __LINE__);
- return -1;
- }
-
- // 3.0 写数据
- FILE *mfp = nullptr;
- mfp = fopen("yk_48000_2_16.pcm", "r");
- if (mfp == nullptr)
- {
- printf("fopen err! \n");
- return -1;
- }
-
- char *buffer = new char[BUFFER_SIZE];
-
- for (;;)
- {
- // write:
- // 一次写20ms的数据
-
- int ret = fread(buffer, 1, BUFFER_SIZE, mfp);
- if (ret < BUFFER_SIZE)
- {
- fseek(mfp, 0, SEEK_SET);
- ret = fread(buffer, 1, BUFFER_SIZE, mfp);
- if (ret < BUFFER_SIZE)
- {
- printf("err to read\n");
- break;
- }
- }
-
- ssize_t result = streamOut->write(streamOut, buffer, BUFFER_SIZE);
- (void)result;
- }
-
- delete[] buffer;
-
- // 4.0 close
- mTest.TearDown();
- printf("hello world!\n");
- }
在\hardware\libhardware\modules\audio_remote_submix\tests 目录下mm, 得到 system/bin/AudioHalPlayer 可执行文件,push到目标设备的 /vendor/bin/ 目录下,同时push进去我们的pcm 文件 yk_48000_2_16.pcm 
完美播放。