目录
一:概述
这是一个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中追加:
//*******
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 <memory>
#include <hardware/audio.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <stdio.h>
#include <stdlib.h>
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
完美播放。