2. 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中追加:

//*******

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   

 完美播放。

猜你喜欢

转载自blog.csdn.net/u012459903/article/details/127745737