此篇博文是笔者验证通过远程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中搜索相关文章。