android8.1 用 Native AudioTrack 直接播放PCM源音频文件

此篇博文是笔者验证通过远程MIC 采集音频输入送至 android8.1 系统进行播放,
可以理解为给本地的安卓系统添加远程的MIC录音功能。

本地播放pcm的音频数据使用libmedia库直接调用native的接口,创建 AudioTrack
播放。

代码Demo如下:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#include <iostream>
#include <fstream>
#include <string>

#include <media/AudioSystem.h>
#include <media/AudioTrack.h>
// #include <media/AudioRecord.h>
// #include <media/AidlConversion.h>
#include <binder/ProcessState.h>
#include <binder/IPCThreadState.h>

#include <cutils/log.h>
#include <cutils/sockets.h>
#include <cutils/properties.h>

#include <arpa/inet.h>
#include <netinet/in.h>

#define LOG_TAG "ImxAudioTrack"
#define LOG_NDEBUG 0

using namespace android;

#define AUDIO_RECORD_SAMPLE 44100

static AudioTrack * pAudioTrack = nullptr;

char* ImxAudioTrack::inBuffer = nullptr;
int ImxAudioTrack::state = 0;
size_t ImxAudioTrack::minFrameCount = 0;

int ImxAudioTrack::g_iInSampleTime = 0;
int ImxAudioTrack::g_iNotificationPeriodInFrames = 4096/10;
bool ImxAudioTrack::g_AudioTrackAction = false;


int ImxAudioTrack::Init()
{
    
    
    String16 opPackageName("com.metis.scrcpy");

    audio_source_t inputSource = AUDIO_SOURCE_HOTWORD; //AUDIO_SOURCE_HOTWORD AUDIO_SOURCE_REMOTE_SUBMIX
    audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT;
    audio_channel_mask_t channelConfig = AUDIO_CHANNEL_OUT_STEREO; //AUDIO_CHANNEL_OUT_STEREO,AUDIO_CHANNEL_OUT_MONO
    
    audio_stream_type_t streamType = AUDIO_STREAM_MUSIC; // AUDIO_STREAM_MUSIC, AUDIO_STREAM_TTS
    audio_session_t sessionId = (audio_session_t)AUDIO_SESSION_ALLOCATE;

    audio_output_flags_t flags = AUDIO_OUTPUT_FLAG_RAW; //@system/media/audio/include/system/audio_base.h

    uid_t uid = -1;
    pid_t pid = -1;
    int sampleRateInHz = AUDIO_RECORD_SAMPLE;
    audio_port_handle_t dev_id = 0;

    audio_attributes_t mAttributes = {
    
    
        /* .content_type = */ AUDIO_CONTENT_TYPE_MUSIC,
        /* .usage = */ (audio_usage_t)AUDIO_USAGE_MEDIA,
        /* .source = */ inputSource,
        /* .flags = */ AUDIO_FLAG_DEEP_BUFFER,            //@system/media/audio/include/system/audio.h
        /* .tags = */ "com.metisinfor.scrcpy"
    };
    g_iInSampleTime = 0;
    g_iNotificationPeriodInFrames = 4096/10;

    android::status_t status = AudioTrack::getMinFrameCount(&minFrameCount, streamType, sampleRateInHz);
    if(status != android::NO_ERROR){
    
    
        ALOGE(" 0x%x AudioTrack.getMinFrameCount() failed", status);
        return -1;
    }

    ALOGD("[%s: %d] streamType = %d minFrameCount = %d audioFormat = 0x%x channelConfig = 0x%x \n",
        __func__,__LINE__, streamType, (int)minFrameCount, audioFormat, channelConfig);

    pAudioTrack  = new android::AudioTrack();
    if( pAudioTrack == nullptr){
    
    
        ALOGE(" create native AudioTrack failed !");
        goto exit;
    }
    sessionId = pAudioTrack->getSessionId();  //> use system create id by ljb

    status = pAudioTrack->set(streamType, 
                              sampleRateInHz, audioFormat, channelConfig,
                              minFrameCount,                      //> size_t frameCount
                              flags,                              //> audio_output_flags_t flags
                              AudioTrackCallback,                 //> callback_t cbf
                              NULL,                               //> void* user
                              0,                                  //> uint32_t notificationFrames 
                              0,                                  //> const sp<IMemory>& sharedBuffer
                              false,                              //> bool threadCanCallJava
                              sessionId, //> audio_session_t sessionId
                              AudioTrack::TRANSFER_SYNC,       //> transfer_type transferType 
                              NULL,                               //> offloadInfo
                              uid,                    
                              pid,
                              &mAttributes,                       //> const audio_attributes_t* mAttributes
                              true,                               //> doNotReconnect
                              1.0f);                               //> maxRequiredSpeed
    
    ALOGD("[%s: %d]  AudioTrack->set() return = %d  \n", __func__, __LINE__, status);

    if(pAudioTrack->initCheck() != android::NO_ERROR){
    
    
        ALOGE(" native AudioTrack initCheck failed !");
        goto exit;
    }

    sessionId = pAudioTrack->getSessionId();
    ALOGD("[%s: %d]  AudioTrack initialized okay, sessionId=%x \n", __func__, __LINE__, sessionId);
    printf("[%s: %d]  AudioTrack initialized okay, sessionId=%x \n", __func__, __LINE__, sessionId);
    dev_id = pAudioTrack->getOutputDevice();
    if(pAudioTrack->setOutputDevice(dev_id) != android::NO_ERROR){
    
    
        ALOGE(" native AudioTrack::setOutputDevice() failed !");
        pAudioTrack->stop();
        pAudioTrack = nullptr;
        return -1;
    }
    printf("[%s: %d] AudioTrackThread initialized okay ... \n", __func__, __LINE__);

    return 0;

exit:
    if(pAudioTrack != nullptr){
    
    
        pAudioTrack->stop();
        pAudioTrack = nullptr;
    }
    return -1;
}

构造audioTrack过程,首先 new android::AudioTrack() 对象,然后通过android::AudioTrack::set()方法来配置该对象;
在检查 initCheck() 该对象是否创建成功,如果成功此AudioTrack就具备播放的能力。
set()方法中使用的回调函数如下:

void ImxAudioTrack::AudioTrackCallback(int event, void * user, void * info)
{
    
    
    if (event != android::AudioTrack::EVENT_MORE_DATA){
    
    
        ALOGD("[%s:%d] not parse event type: 0x%x \n",__func__,__LINE__, event);
    }
    ALOGD("[%s:%d] parse event type: 0x%x \n",__func__,__LINE__, event);
    android::AudioTrack::Buffer* pBuff = (android::AudioTrack::Buffer*)info;
    if(inBuffer != nullptr && pAudioTrack != nullptr){
    
    
        state = pAudioTrack->write(inBuffer,minFrameCount,true);
        ALOGD("[%s:%d] write AudioTrack data resp: 0x%x \n",__func__,__LINE__, state);
    }
}

然后启动线程给 AudioTrack播放器喂音频数据,如下:


void ImxAudioTrack::IPC_disconnect_handle_pipe(int signo)
{
    
    
    printf("[%s: %d] write socket when peer disconnect pipe  sig:%d \n", __func__, __LINE__, signo);
    g_AudioTrackAction = false;
}

#define DEBUG_AUDIO_TRACK 1

void ImxAudioTrack::AudioTrackThread(){
    
    
    printf("[%s: %d] AudioTrackThread startup ... \n", __func__, __LINE__);
    ALOGD("[%s: %d]  AudioTrackThread startup ... \n", __func__, __LINE__);
    
    int rc = 0, count = 0; 
    socklen_t len = 0;
    ssize_t ret = 0;
    ssize_t readLen = 0;

    int iNbChannels;            // 1 channel for mono, 2 channel for streo
    int iBytesPerSample = 2;    //  16bits pcm, 2Bytes
    int frameSize = 0;          //  frameSize = iNbChannels * iBytesPerSample
    //> debug start
    const char* filePath = "/data/local/tmp/AudioRecordFile.pcm";
    std::fstream  pcmFile;
    int wLen = 0;
    this->isConnected = false;  //> debug function 
    int BUFFER_MAX_SIZE = 1024*2;
    //> end

    struct sigaction sa;
    sa.sa_handler = IPC_disconnect_handle_pipe;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGPIPE, &sa, NULL);

    this->g_bQuitAudioRecordThread = false;

    while(g_bQuitAudioRecordThread == false){
    
    
        rc = this->Init();
        if(rc < 0){
    
    
            ALOGE("audioRecord initialized error! \n");
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
            continue;
        }

#if DEBUG_AUDIO_TRACK
        ALOGV("[%s: %d] enter debug mode ...\n", __func__, __LINE__);
#else
        ALOGD("[%s: %d] wait for connected ...\n", __func__, __LINE__);
        while(this->isConnected == false){
    
    
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
#endif
        iNbChannels = 1;//(channelConfig == AUDIO_CHANNEL_IN_STEREO) ? 2 : 1;
        frameSize   = iNbChannels * iBytesPerSample;
        this->bufferSizeInBytes = minFrameCount * frameSize;
        inBuffer = (char*)malloc(this->bufferSizeInBytes);
        if(inBuffer == nullptr){
    
    
            ALOGE("[%s: %d]  malloc meme failed  \n", __func__, __LINE__);
            break;
        }

        rc = this->start();
        if(rc < 0 ){
    
    
            ALOGE("AudioTrack start failed!!! \n");
            break;
        }
        ALOGD("[%s: %d] socket connected, startup AudioTrack write\n", __func__, __LINE__);
        count = 0;
        int length;
        g_AudioTrackAction = true;

#if DEBUG_AUDIO_TRACK
        //> debug function 
        pcmFile.open(filePath,std::ios::in);
        if(!pcmFile){
    
    
            printf("[%s: %d]  open file %s error! \n", __func__, __LINE__,filePath);
            break;
        }
        pcmFile.seekg(0,std::ios::end);
        length = pcmFile.tellg();
        if(length < 1){
    
    
            printf("[%s: %d]  file %s length error! \n", __func__, __LINE__,filePath);
            break;
        }
        printf("[%s: %d]  file %s length=%d kB\n", __func__, __LINE__,filePath,length/1024);
        ALOGD("[%s: %d]  file %s length=%d kB\n", __func__, __LINE__,filePath,length/1024);
        pcmFile.seekg(0,std::ios::beg);
        this->bufferSizeInBytes = 1024;
#endif
        while(g_AudioTrackAction == true){
    
    
            // std::this_thread::sleep_for(std::chrono::milliseconds(500));
#if DEBUG_AUDIO_TRACK
            while(length > 0){
    
    
                if(length > bufferSizeInBytes){
    
    
                    wLen = bufferSizeInBytes;
                }else{
    
    
                    wLen = length;
                }
                pcmFile.read(inBuffer, wLen);
                //ALOGD(" %s write audio track data len=%d \n", __func__, wLen);
                rc = pAudioTrack->write(inBuffer,wLen,true);
                if(rc < 0){
    
    
                    ALOGE("Error:[%s: %d] write  audio track data error,code=%x \n", __func__, __LINE__,rc);
                    printf("Error:[%s: %d] audio track write error,CODE=%x \n", __func__, __LINE__,rc);
                    pcmFile.close();
                    break;
                }
                length -= wLen;            
                ALOGD(" %s write audio track data len= %d ,remaining:%d \n", __func__, wLen, length);
                
            }
            g_AudioTrackAction = false;
            g_bQuitAudioRecordThread = true;
#else
            readLen = read(this->sockfd, inBuffer, minFrameCount);
            while(readLen > 0){
    
    
                ret = pAudioTrack->write(inBuffer, readLen, true);
                if(ret <= 0){
    
    
                    ALOGE("Error:[%s: %d]  write data stream error \n", __func__, __LINE__);
                    break;
                }
                ALOGD("[%s: %d]  write stream count: %d , data-len: %zd \n", __func__, __LINE__, count++, ret);
                readLen -= ret;
            }

            if(readLen < 0 || ret < 0){
    
    
                ALOGE("[%s: %d]  AudioTrack write error! \n", __func__, __LINE__);
                break;
            }
#endif            
        }

        free(inBuffer);
        close(this->sockfd);
        this->stop();
        this->isConnected = false;
        ALOGD("[%s: %d]  Audio Track restart  ... \n", __func__, __LINE__);
        //this->g_bQuitAudioRecordThread = true;
    }
    
exitThread:
    close(this->sockfd);
    this->stop();
    pAudioTrack = nullptr;
    printf("[%s: %d]  AudioTrack thread exit ... \n", __func__, __LINE__);
    ALOGD("[%s: %d]  AudioTrack thread exit ... \n", __func__, __LINE__);
    return;
}

线程中可以通过网络获取音频源数据,也可以读取本地pcm文件内容,直接把音频数据送入至
AudioTrack播放器中。
接下来给AudioTrack播放器增加play和stop方法,就构建一个基础AudioTrack播放器框架,
如下:

int ImxAudioTrack::start(){
    
    
    if(pAudioTrack != nullptr){
    
    
        android::status_t status = pAudioTrack->start();
        return status;
    } else {
    
    
        ALOGE(" pAudioRecord->start() is nullptr \n");
        return -2;
    }
}

void ImxAudioTrack::stop(){
    
    
    if(pAudioTrack != nullptr){
    
    
        pAudioTrack->stop();
    } else {
    
    
        ALOGE(" pAudioTrack->stop() is nullptr \n");
    }
}

void ImxAudioTrack::exitInstance(){
    
    
    this->g_bQuitAudioRecordThread = true;
    this->g_AudioTrackAction = false;
}

void ImxAudioTrack::getInstance(){
    
    
    if(this->g_AudioTrackAction == false){
    
    
        this->g_AudioTrackAction = true;
        this->mAudioTrackThread = CreateAudioTrackThread();
        //this->mAudioTrackThread.detach();
    }
}

测试用例

如何使用此 AudioTrack 播放器呢,下面测试例程:

int main(){
    
    
    int count = 0;
    bool running = true;
    ImxAudioTrack *pIAT = new ImxAudioTrack();
    if(pIAT == nullptr){
    
    
        printf("Error:[%s: %d]  create ImxAudioTrack error \n", __func__, __LINE__);
        return 0;
    }
    pIAT->getInstance();

    while(running){
    
    
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        count += 1;
        // if(count > 660){
    
    
        //     pIAT->exitInstance();
        //     running = false;
        // }
    }
    printf("[%s: %d]  exit ImxAudioTrack thread \n", __func__, __LINE__);
    return 0;
}

如何把此部分内容编译到 android8.1 的镜像中呢,请参考下面Android.mk 文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := audio_track_engine
LOCAL_MODULE_TAGS := optional

LOCAL_SRC_FILES := \
    ImxAudioTrack.cpp \

LOCAL_C_INCLUDES += \
    $(LOCAL_PATH) \
    $(KERNEL_HEADERS) \
    frameworks/av/include \
    frameworks/native/include \
    frameworks/av/include/media \
    frameworks/av/services/audioflinger \
    frameworks/av/services/audiopolicy \
    frameworks/av/services/audiopolicy/common/managerdefinitions/include \
    frameworks/av/services/audiopolicy/common/include \
    frameworks/av/services/audiopolicy/engine/interface \
    frameworks/av/services/audiopolicy/service \
    frameworks/av/services/medialog \
    frameworks/av/services/soundtrigger \

LOCAL_SHARED_LIBRARIES:= \
    liblog libutils libcutils \
    libbinder \
    libmedia \
    libaudioutils \
    libaudiomanager \
    libaudioclient \
    libmediametrics \

LOCAL_CFLAGS += -g -DBUILD_FOR_ANDROID
#LOCAL_CFLAGS += -Wall -Werror
LOCAL_LD_FLAGS += -nostartfiles

LOCAL_PRELINK_MODULE := false
$(info  "LOCAL_MODULE =  $(LOCAL_MODULE)")
include $(BUILD_EXECUTABLE)

include $(call all-makefiles-under,$(LOCAL_PATH))

编译后就可以通过console直接运行就可以验证。此代码中涉及到的static变量的使用方法,
特此把头文件也贴出来,供有需求的小伙伴参考。

#ifndef _IMX_AUDIO_TRACK_H_
#define _IMX_AUDIO_TRACK_H_

#include<thread>
#include<vector>
#include<queue>
#include<functional>

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>



//namespace android {
    
    


class ImxAudioTrack {
    
    

public:
    ImxAudioTrack(){
    
    this->isConnected=false;}
    ~ImxAudioTrack(){
    
    }

private:

    const char* SOCKETNAME="virtual_sound_socket";
    int bufferSizeInBytes = 1024*2;

    int sockfd = -1;

    int Init();
    
    int start();

    void stop();

    int ReadIfstream();

    bool isConnected = false;
    bool g_bQuitAudioRecordThread;

    std::thread mAudioTrackThread;
    void AudioTrackThread(void);
    std::thread CreateAudioTrackThread(){
    
    
        return std::thread(&ImxAudioTrack::AudioTrackThread, this);
    }

public:
    static int g_iInSampleTime;
    static int g_iNotificationPeriodInFrames;
    static int state;
    static bool g_AudioTrackAction;

    static size_t minFrameCount;
    static char* inBuffer;

    static void AudioTrackCallback(int event, void * user, void * info);
    static void IPC_disconnect_handle_pipe(int signo);

    void getInstance();

    void exitInstance();

    void setNetworkStatus(int sock){
    
    
        this->sockfd = sock;
        isConnected = true;
    }
    void clrNetworkStatus(void){
    
    
        isConnected = false;
    }

};

//}; // namespace

#endif //IMX_AUDIO_TRACK_H_

至此,本文内容就介绍完毕;笔者对此方法进行总结:
通过libmedia库的方式,AudioTrack播放音频路由策略、声音焦点属性,还是在Android系统的框架下管理;
如果你产品需求是不需要在框架中管理,可采用libtinyalsa直接控制声卡的方式,实现录音与播放,如用
着方面需求,请在笔者blog中搜索相关文章。

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/126098676