• <二.1> android 直接使用hal库播放pcm demo


    目录

    一:概述

    二:实现

    环境

    原理说明:

    注意问题:

    上源代码:

    编译执行:


    一:概述

    这是一个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"


    注意问题:

    • 需要root,需要remount, 需要push到 /vendor/bin/  目录下运行。 个人开始放到/data 目录下,会出现加载 hal so库的时候,dlopen失败,估计时路径权限问题 。这个在remote_submix_tests.cpp 源码文件中就有说明,要push到机器上的/vendor/bin 目录

    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 播放的时候,音量大小是不受系统的音量大小控制的,始终是最大的音量(这说明 aosp的这个系统音量设置应该是设置的 AudioFlinger里面的软音量。 跟具体的设备和系统有关,某些车机系统可能就是直接修改hal层的硬件音量,现象就不一样了
    •  改demo运行会导致系统相册里面播放器直接暂停, (所以是不是不支持 hal层重入?同一时刻只能有一个进程打开?

    上源代码:

    为简单起见,本demo直接利用源码中 audio_remote_submix /tests 的Android.bp  在该Android.bp中追加:

    1. //*******
    2. cc_binary {
    3. name: "AudioHalPlayer",
    4. srcs: ["AudioHalPlayer.cpp"],
    5. shared_libs: [
    6. "libhardware",
    7. "liblog",
    8. "libutils",
    9. ],
    10. cflags: ["-Wall", "-Werror", "-O0", "-g",],
    11. header_libs: ["libaudiohal_headers"],
    12. }

    添加源码 cpp 文件:AudioHalPlayer.cpp

    1. // To run this test (as root):
    2. // 1) Build it
    3. // 2) adb push to /vendor/bin
    4. // 3) adb shell /vendor/bin/r_submix_tests
    5. #define LOG_TAG "AudioHalPlayer"
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. using namespace android;
    13. static status_t load_audio_interface(const char *if_name, audio_hw_device_t **dev)
    14. {
    15. const hw_module_t *mod;
    16. int rc;
    17. rc = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID, if_name, &mod);
    18. if (rc)
    19. {
    20. printf("%s couldn't load audio hw module %s.%s (%s) \n", __func__,
    21. AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
    22. goto out;
    23. }
    24. rc = audio_hw_device_open(mod, dev);
    25. if (rc)
    26. {
    27. printf("%s couldn't open audio hw device in %s.%s (%s) \n", __func__,
    28. AUDIO_HARDWARE_MODULE_ID, if_name, strerror(-rc));
    29. goto out;
    30. }
    31. if ((*dev)->common.version < AUDIO_DEVICE_API_VERSION_MIN)
    32. {
    33. printf("%s wrong audio hw device version %04x \n", __func__, (*dev)->common.version);
    34. rc = BAD_VALUE;
    35. audio_hw_device_close(*dev);
    36. goto out;
    37. }
    38. return OK;
    39. out:
    40. *dev = NULL;
    41. return rc;
    42. }
    43. class AudioHalPlayer
    44. {
    45. public:
    46. void SetUp()
    47. {
    48. mDev = nullptr;
    49. // audio.h
    50. // #define AUDIO_HARDWARE_MODULE_ID_PRIMARY "primary"
    51. // #define AUDIO_HARDWARE_MODULE_ID_A2DP "a2dp"
    52. // #define AUDIO_HARDWARE_MODULE_ID_USB "usb"
    53. // #define AUDIO_HARDWARE_MODULE_ID_REMOTE_SUBMIX "r_submix"
    54. // #define AUDIO_HARDWARE_MODULE_ID_CODEC_OFFLOAD "codec_offload"
    55. // #define AUDIO_HARDWARE_MODULE_ID_STUB "stub"
    56. // #define AUDIO_HARDWARE_MODULE_ID_HEARING_AID "hearing_aid"
    57. // #define AUDIO_HARDWARE_MODULE_ID_MSD "msd"
    58. load_audio_interface(AUDIO_HARDWARE_MODULE_ID_PRIMARY, &mDev);
    59. printf("[%s%d] %d \n", __FUNCTION__, __LINE__, (mDev == nullptr));
    60. }
    61. void TearDown()
    62. {
    63. if (mDev != nullptr)
    64. {
    65. int status = audio_hw_device_close(mDev);
    66. mDev = nullptr;
    67. (void)status;
    68. }
    69. }
    70. void OpenOutputStream(
    71. const char *address, bool mono, uint32_t sampleRate, audio_stream_out_t **streamOut)
    72. {
    73. *streamOut = nullptr;
    74. struct audio_config configOut = {};
    75. configOut.channel_mask = mono ? AUDIO_CHANNEL_OUT_MONO : AUDIO_CHANNEL_OUT_STEREO;
    76. configOut.sample_rate = sampleRate;
    77. if (mDev == nullptr)
    78. {
    79. printf("[%s%d] mDev==null\n", __FUNCTION__, __LINE__);
    80. return;
    81. }
    82. status_t result = mDev->open_output_stream(mDev,
    83. AUDIO_IO_HANDLE_NONE, AUDIO_DEVICE_NONE, AUDIO_OUTPUT_FLAG_NONE,
    84. &configOut, streamOut, address);
    85. (void)result;
    86. }
    87. audio_hw_device_t *mDev;
    88. };
    89. // 48k, 16bit(16/8 字节), 这里我们按照该每次写20ms的数据。( 1/50 秒), 双声道(*2)
    90. #define BUFFER_SIZE (48000 * 16 / 8 / 50 * 2)
    91. int main()
    92. {
    93. AudioHalPlayer mTest;
    94. // 1.0 // 加载so打开设备
    95. mTest.SetUp();
    96. // 2.0 // 打开流
    97. const char *address = "1";
    98. audio_stream_out_t *streamOut = nullptr;
    99. mTest.OpenOutputStream(address, false /*mono*/, 48000, &streamOut);
    100. if (streamOut == nullptr)
    101. {
    102. printf("[%s%d] streamOut==null\n", __FUNCTION__, __LINE__);
    103. return -1;
    104. }
    105. // 3.0 写数据
    106. FILE *mfp = nullptr;
    107. mfp = fopen("yk_48000_2_16.pcm", "r");
    108. if (mfp == nullptr)
    109. {
    110. printf("fopen err! \n");
    111. return -1;
    112. }
    113. char *buffer = new char[BUFFER_SIZE];
    114. for (;;)
    115. {
    116. // write:
    117. // 一次写20ms的数据
    118. int ret = fread(buffer, 1, BUFFER_SIZE, mfp);
    119. if (ret < BUFFER_SIZE)
    120. {
    121. fseek(mfp, 0, SEEK_SET);
    122. ret = fread(buffer, 1, BUFFER_SIZE, mfp);
    123. if (ret < BUFFER_SIZE)
    124. {
    125. printf("err to read\n");
    126. break;
    127. }
    128. }
    129. ssize_t result = streamOut->write(streamOut, buffer, BUFFER_SIZE);
    130. (void)result;
    131. }
    132. delete[] buffer;
    133. // 4.0 close
    134. mTest.TearDown();
    135. printf("hello world!\n");
    136. }

    编译执行:

    在\hardware\libhardware\modules\audio_remote_submix\tests 目录下mm, 得到  system/bin/AudioHalPlayer 可执行文件,push到目标设备的 /vendor/bin/ 目录下,同时push进去我们的pcm 文件 yk_48000_2_16.pcm   

     完美播放。

  • 相关阅读:
    【JIRA 学习】如何配置工作流转换的时候触发弹出界面,并填写相关信息
    Vue 打包成桌面应用 vue 打包桌面应用 vue部署为桌面应用 vue部署桌面应用 vue 桌面应用
    【Linux】部署单机OA项目及搭建spa前后端分离项目
    【 C++ 】unordered_map和unordered_set的介绍和使用
    WPF双滑块控件以及强制捕获鼠标事件焦点
    夯实c语言基础
    一次生产死锁问题的处理
    想要精通算法和SQL的成长之路 - 最长回文子串
    实测有效:windows下文件名太长的问题,git filename too long
    【《雨夜》 RocketMQ源码系列(一) NameServer 核心源码解析】
  • 原文地址:https://blog.csdn.net/u012459903/article/details/127745737