Android音频(10)——多App同时录音实现 ---包含pcm转wave源码

Android音频(10)——多App同时录音实现
转自:https://www.cnblogs.com/hellokitty2/p/10947364.html
一、使用c++编写录音程序

  1. PCM音频数据是原始音频数据,无法使用播放器播放,需要给它加上一个头部,表明声音有几个通道,采样率是多少等等。将
    PCM音频数据转换为WAV格式,这样其它播放器才能播放出来。

  2. 录音时要确定3个参数
    (1)采样率:一秒钟对声波采样的次数。常用的采样率有8000,11025,22050,32000,44100.
    高版本的Android应该可以支持更高的采样率。

(2)每个采样值使用多少bit表示
目前Android系统上固定为16bit

(3)声道数
Stereo:立体声,每个采样点记录左右声道的值
Mono: 单声道

  1. tinyplay工具只能播放双声道的音频数据。

4.测试程序
(1)AudioRecordTest.cpp,用于不做pcm数据

复制代码
#include <utils/Log.h>
#include <media/AudioRecord.h>
#include <stdlib.h>

using namespace android;

//==============================================
// Audio Record Defination
//==============================================

#ifdef LOG_TAG
#undef LOG_TAG
#endif

#define LOG_TAG “AudioRecordTest”

static pthread_t g_AudioRecordThread;
static pthread_t * g_AudioRecordThreadPtr = NULL;

volatile bool g_bQuitAudioRecordThread = false;
volatile int g_iInSampleTime = 0;
int g_iNotificationPeriodInFrames = 8000/10;
// g_iNotificationPeriodInFrames should be change when sample rate changes.

static void * AudioRecordThread(int sample_rate, int channels, void *fileName)
{
uint64_t inHostTime = 0;
void * inBuffer = NULL;
audio_source_t inputSource = AUDIO_SOURCE_MIC;
audio_format_t audioFormat = AUDIO_FORMAT_PCM_16_BIT;
audio_channel_mask_t channelConfig = AUDIO_CHANNEL_IN_MONO;
int bufferSizeInBytes;
int sampleRateInHz = sample_rate; //8000; //44100;
android::AudioRecord * pAudioRecord = NULL;
FILE * g_pAudioRecordFile = NULL;
char * strAudioFile = (char *)fileName;

int iNbChannels         = channels;    // 1 channel for mono, 2 channel for streo
int iBytesPerSample = 2;     // 16bits pcm, 2Bytes
int frameSize             = 0;    // frameSize = iNbChannels * iBytesPerSample
size_t  minFrameCount     = 0;    // get from AudroRecord object
int iWriteDataCount = 0;    // how many data are there write to file

// log the thread id for debug info
ALOGD("%s  Thread ID  = %d  \n", __FUNCTION__,  pthread_self());  
g_iInSampleTime = 0;
g_pAudioRecordFile = fopen(strAudioFile, "wb+");    

//printf("sample_rate = %d, channels = %d, iNbChannels = %d, channelConfig = 0x%x\n", sample_rate, channels, iNbChannels, channelConfig);

//iNbChannels = (channelConfig == AUDIO_CHANNEL_IN_STEREO) ? 2 : 1;
if (iNbChannels == 2) {
    channelConfig = AUDIO_CHANNEL_IN_STEREO;
}
printf("sample_rate = %d, channels = %d, iNbChannels = %d, channelConfig = 0x%x\n", sample_rate, channels, iNbChannels, channelConfig);

frameSize     = iNbChannels * iBytesPerSample;    

android::status_t     status = android::AudioRecord::getMinFrameCount(
    &minFrameCount, sampleRateInHz, audioFormat, channelConfig);    

if(status != android::NO_ERROR)
{
    ALOGE("%s  AudioRecord.getMinFrameCount fail \n", __FUNCTION__);
    goto exit ;
}

ALOGE("sampleRateInHz = %d minFrameCount = %d iNbChannels = %d channelConfig = 0x%x frameSize = %d ", 
    sampleRateInHz, minFrameCount, iNbChannels, channelConfig, frameSize);    

bufferSizeInBytes = minFrameCount * frameSize;

inBuffer = malloc(bufferSizeInBytes); 
if(inBuffer == NULL)
{        
    ALOGE("%s  alloc mem failed \n", __FUNCTION__);        
    goto exit ; 
}

g_iNotificationPeriodInFrames = sampleRateInHz/10;    

pAudioRecord  = new android::AudioRecord();    
if(NULL == pAudioRecord)
{
    ALOGE(" create native AudioRecord failed! ");
    goto exit;
}

pAudioRecord->set( inputSource,
                                sampleRateInHz,
                                audioFormat,
                                channelConfig,
                                0,
                                NULL, //AudioRecordCallback,
                                NULL,
                                0,
                                true,
                                0); 

if(pAudioRecord->initCheck() != android::NO_ERROR)  
{
    ALOGE("AudioTrack initCheck error!");
    goto exit;
}
 
if(pAudioRecord->start()!= android::NO_ERROR)
{
    ALOGE("AudioTrack start error!");
    goto exit;
}    

while (!g_bQuitAudioRecordThread)
{
    int readLen = pAudioRecord->read(inBuffer, bufferSizeInBytes);        
    int writeResult = -1;
    
    if(readLen > 0) 
    {
        iWriteDataCount += readLen;
        if(NULL != g_pAudioRecordFile)
        {
            writeResult = fwrite(inBuffer, 1, readLen, g_pAudioRecordFile);                
            if(writeResult < readLen)
            {
                ALOGE("Write Audio Record Stream error");
            }
        }            

        //ALOGD("readLen = %d  writeResult = %d  iWriteDataCount = %d", readLen, writeResult, iWriteDataCount);            
    }
    else 
    {
        ALOGE("pAudioRecord->read  readLen = 0");
    }
}

exit:
if(NULL != g_pAudioRecordFile)
{
fflush(g_pAudioRecordFile);
fclose(g_pAudioRecordFile);
g_pAudioRecordFile = NULL;
}

if(pAudioRecord)
{
    pAudioRecord->stop();
    //delete pAudioRecord;
    //pAudioRecord == NULL;
}

if(inBuffer)
{
    free(inBuffer);
    inBuffer = NULL;
}

ALOGD("%s  Thread ID  = %d  quit\n", __FUNCTION__,  pthread_self());
return NULL;

}

int main(int argc, char **argv)
{
if (argc != 4)
{
printf(“Usage:\n”);
printf("%s <sample_rate> <out_file>\n", argv[0]);
return -1;
}
AudioRecordThread(strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0), argv[3]);
return 0;
}
复制代码
(2)pcm2wav.cpp,用于将pcm转换为wav格式

复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* https://blog.csdn.net/u010011236/article/details/53026127 */

/**

  • Convert PCM16LE raw data to WAVE format

  • @param pcmpath Input PCM file.

  • @param channels Channel number of PCM file.

  • @param sample_rate Sample rate of PCM file.

  • @param wavepath Output WAVE file.
    */
    int simplest_pcm16le_to_wave(const char *pcmpath, int sample_rate, int channels, const char *wavepath)
    {
    typedef struct WAVE_HEADER{
    char fccID[4]; //ÄÚÈÝΪ"“RIFF
    unsigned long dwSize; //×îºóÌîд£¬WAVE¸ñʽÒôƵµÄ´óС
    char fccType[4]; //ÄÚÈÝΪ"WAVE”
    }WAVE_HEADER;

    typedef struct WAVE_FMT{
    char fccID[4]; //ÄÚÈÝΪ"fmt "
    unsigned long dwSize; //ÄÚÈÝΪWAVE_FMTÕ¼µÄ×Ö½ÚÊý£¬Îª16
    unsigned short wFormatTag; //Èç¹ûΪPCM£¬¸ÄֵΪ 1
    unsigned short wChannels; //ͨµÀÊý£¬µ¥Í¨µÀ=1£¬Ë«Í¨µÀ=2
    unsigned long dwSamplesPerSec;//²ÉÓÃƵÂÊ
    unsigned long dwAvgBytesPerSec;/* ==dwSamplesPerSecwChannelsuiBitsPerSample/8 /
    unsigned short wBlockAlign;//==wChannels
    uiBitsPerSample/8
    unsigned short uiBitsPerSample;//ÿ¸ö²ÉÑùµãµÄbitÊý£¬8bits=8, 16bits=16
    }WAVE_FMT;

    typedef struct WAVE_DATA{
    char fccID[4]; //ÄÚÈÝΪ"data"
    unsigned long dwSize; //==NumSampleswChannelsuiBitsPerSample/8
    }WAVE_DATA;

#if 0
if(channels2 || sample_rate0)
{
channels = 2;
sample_rate = 44100;
}
#endif
int bits = 16;

WAVE_HEADER pcmHEADER;
WAVE_FMT    pcmFMT;
WAVE_DATA   pcmDATA;

unsigned short m_pcmData;
FILE *fp, *fpout;

fp = fopen(pcmpath, "rb+");
if(fp==NULL)
{
    printf("Open pcm file error.\n");
    return -1;
}
fpout = fopen(wavepath, "wb+");
if(fpout==NULL)
{
    printf("Create wav file error.\n");
    return -1;
}

/* WAVE_HEADER */
memcpy(pcmHEADER.fccID, "RIFF", strlen("RIFF"));
memcpy(pcmHEADER.fccType, "WAVE", strlen("WAVE"));
fseek(fpout, sizeof(WAVE_HEADER), 1);   //1=SEEK_CUR
/* WAVE_FMT */
memcpy(pcmFMT.fccID, "fmt ", strlen("fmt "));
pcmFMT.dwSize = 16;
pcmFMT.wFormatTag = 1;
pcmFMT.wChannels = channels;
pcmFMT.dwSamplesPerSec = sample_rate;
pcmFMT.uiBitsPerSample = bits;
/* ==dwSamplesPerSec*wChannels*uiBitsPerSample/8 */
pcmFMT.dwAvgBytesPerSec = pcmFMT.dwSamplesPerSec*pcmFMT.wChannels*pcmFMT.uiBitsPerSample/8;
/* ==wChannels*uiBitsPerSample/8 */
pcmFMT.wBlockAlign = pcmFMT.wChannels*pcmFMT.uiBitsPerSample/8;


fwrite(&pcmFMT, sizeof(WAVE_FMT), 1, fpout);

/* WAVE_DATA */
memcpy(pcmDATA.fccID, "data", strlen("data"));
pcmDATA.dwSize = 0;
fseek(fpout, sizeof(WAVE_DATA), SEEK_CUR);

fread(&m_pcmData, sizeof(unsigned short), 1, fp);
while(!feof(fp))
{
    pcmDATA.dwSize += 2;
    fwrite(&m_pcmData, sizeof(unsigned short), 1, fpout);
    fread(&m_pcmData, sizeof(unsigned short), 1, fp);
}

/*pcmHEADER.dwSize = 44 + pcmDATA.dwSize;*/
//ÐÞ¸Äʱ¼ä£º2018Äê1ÔÂ5ÈÕ
pcmHEADER.dwSize = 36 + pcmDATA.dwSize;

rewind(fpout);
fwrite(&pcmHEADER, sizeof(WAVE_HEADER), 1, fpout);
fseek(fpout, sizeof(WAVE_FMT), SEEK_CUR);
fwrite(&pcmDATA, sizeof(WAVE_DATA), 1, fpout);

fclose(fp);
fclose(fpout);

return 0;

}

int main(int argc, char **argv)
{
if (argc != 5)
{
printf(“Usage:\n”);
printf("%s <sample_rate> \n", argv[0]);
return -1;
}

simplest_pcm16le_to_wave(argv[1], strtol(argv[2], NULL, 0), strtol(argv[3], NULL, 0), argv[4]);

return 0;

}
复制代码
(3)Android.mk

复制代码
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:=
AudioRecordTest.cpp

LOCAL_SHARED_LIBRARIES :=
libcutils
libutils
libmedia

LOCAL_MODULE:= audio_record_test

LOCAL_MODULE_TAGS := tests

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_SRC_FILES:=
pcm2wav.cpp

LOCAL_SHARED_LIBRARIES :=
libcutils
libutils
libmedia

LOCAL_MODULE:= pcm2wav

LOCAL_MODULE_TAGS := tests

include $(BUILD_EXECUTABLE)
复制代码
然后使用tinyplay播放产生的wav文件。

录音程序参考:
Android Native C++ 层中使用AudioRecord录制PCM音频: https://blog.csdn.net/romantic_energy/article/details/50521970

pcm转wav参考:
PCM、WAV格式介绍及用C语言实现PCM转WAV: https://blog.csdn.net/u010011236/article/details/53026127

  1. 耳机的只有一边播放有声音的原因

./AudioRecordTest 44100 2 my.pcm
./pcm2wav my.pcm 44100 2 my.wav
tinyplay my.wav
只有1个耳朵都听到声音

./AudioRecordTest 44100 1 my.pcm
./pcm2wav my.pcm 44100 1 my.wav
tinyplay 不能播放单声道声音, 用其他播放器来播放my.wav,2个耳朵都听到声音

为何录音时用双声通,播放时只有1个耳朵有声音?
反而录音时用单声通,播放时2个耳朵都有声音?

答案:
a. 硬件上、驱动上是双声道的; 但是我们只接了一个MIC,所以驱动程序录音时得到的双声道数据中,其中一个声道数据恒为0
b. AudioRecordTest录音时如果指定了双声道,那么得到的PCM数据里其中一个声道恒为0,它播放时就会导致只有一个耳朵有声音
c. AudioRecordTest录音时如果指定了单声道,那么得到的PCM数据只含有一个声道数据,它是硬件左、右声道的混合,这个混合
是AudioFlinger系统实现的.在播放时单声道数据时, AudioFlinger系统会把单声道数据既发给硬件Left DAC(左声道)、也发给硬
件Right DAC(右声道),所以2个耳朵都可以听到声音

二、录音框架及代码流程

  1. playbackThread 就是MixerThread,多个App对应着一个线程。

  2. 原生的Android录音流程
    根据App传入的声音来源找到对应的device
    找到profile(audio_policy.conf产生的)
    根据profile找到module,即对应一个声卡,然后加载对应声卡的HAL文件
    调用HAL文件中的openInput()来打开一个输入通道。

  3. 录音时只要App执行了set(),就会创建一个RecordThread(),多个App可能导致并发访问声卡,导致竞争访
    问声卡数据的问题。

  4. 录音框架及代码流程
    a. APP创建、设置AudioRecord, 指定了声音来源: inputSource, 比如: AUDIO_SOURCE_MIC,还指定了采样率、通道数、格式等参数
    b. AudioPolicyManager根据inputSource等参数确定录音设备: device
    c. AudioFlinger创建一个RecordThread, 以后该线程将从上述device读取声音数据
    d. 在RecordThread内部为APP的AudioRecord创建一个对应的RecordTrack,APP的AudioRecord 与 RecordThread内部的RecordTrack 通过共享内存传递数据
    e. RecordThread从HAL中得到数据, 再通过内部的RecordTrack把数据传给APP的AudioRecord

注意:
在原生代码中,APP的一个AudioRecord会导致创建一个RecordThread,在一个device上有可能存在多个RecordThread,
任意时刻只能有一个RecordThread在运行,所以只能有一个APP在录音,不能多个APP同时录音

三、修改代码支持多APP同时录音

修改AudioPolicyManager.cpp,补丁如下:

复制代码
Subject: [PATCH] v2, support Multi AudioRecord at same time


AudioPolicyManager.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/AudioPolicyManager.cpp b/AudioPolicyManager.cpp
index 536987a…6c87508 100644
— a/AudioPolicyManager.cpp
+++ b/AudioPolicyManager.cpp
@@ -1356,6 +1356,17 @@ audio_io_handle_t AudioPolicyManager::getInput(audio_source_t inputSource,
config.channel_mask = channelMask;
config.format = format;

  • /* check wether have an AudioInputDescriptor use the same profile */
  • for (size_t input_index = 0; input_index < mInputs.size(); input_index++) {
  •    sp<AudioInputDescriptor> desc;
    
  •    desc = mInputs.valueAt(input_index);
    
  •    if (desc->mProfile == profile) {
    
  •        desc->mOpenRefCount++;        // ÒýÓüÆÊý¼Ó1    
    
  •        desc->mSessions.add(session); // session
    
  •        return desc->mIoHandle;
    
  •    }
    
  • }
  • status_t status = mpClientInterface->openInput(profile->mModule->mHandle,
    &input,
    &config,

    1.9.1
    复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值