天天看點

08.音頻系統:第008課_項目實戰2_多APP同時錄音:第001節_使用c++編寫錄音程式

在android5.0,6.0,7.0,8.0中,都沒有實作多個APP同時錄音的功能,假設有一個應用程式在錄音,其他的APP則不能再錄音。我們先寫一個C++的錄音程式。然後在講解錄音的架構,以及代碼流程。

使用C++編寫一個程式,他可以獲得原始的音頻資料,但是這些原始的資料是沒有辦法使用播放器直接播放的,需要給他加上一個頭部,表明聲音有幾個通道,采樣率是多少等等。是以需要把pcm格式轉化為Wav格式。這樣其他的播放器才能播放,

錄音時,我們需要知道

1.采樣率:一般android采樣率有8000,11025,22050,32000,44100.。

2.每個采樣值用多少位(fomat)來表示,android一般使用16位

3.通道,左右聲道,當然有的時候也隻采樣一個通道。

4.每個采樣點,記錄兩個值,分别為左右聲道,每個值用16位表示,是以每個采樣點為32位。

即在采樣的時候我們隻需要提供3個參數:采樣率,格式(format),以及通道數。

下面建立一個檔案夾,編寫如下代碼APP_0011_AudioRecordTest:

AudioRecordTest.cpp

#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> <channels> <out_file>\n", argv[0]);
		return -1;
	}
	AudioRecordThread(strtol(argv[1], NULL, 0), strtol(argv[2], NULL, 0), argv[3]);
	return 0;
}


           

pcm2wav.cpp

#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;/* ==dwSamplesPerSec*wChannels*uiBitsPerSample/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;   //==NumSamples*wChannels*uiBitsPerSample/8
    }WAVE_DATA;

#if 0
    if(channels==2 || sample_rate==0)
    {
        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 <input pcm file> <sample_rate> <channels>  <output wav file>\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;
}

           

Android.mk

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= \
	AudioRecordTest.cpp

LOCAL_SHARED_LIBRARIES := \
	libcutils \
	libutils \
    libmedia

LOCAL_MODULE:= AudioRecordTest

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)

           

編寫完成之後,把檔案夾拷貝到frameworks\testing下,使用mmm指令編譯,重新燒寫system.img鏡像。然後在開發闆上執行:

record audio:      ./AudioRecordTest 44100 2 my.pcm  
   
covert pcm to wav:      ./pcm2wav my.pcm 44100 2 my.wav  
  
play wav: tinyplay my.wav  
           

其上的tinyplay隻能播放雙通道的資料。

當我們運作兩個錄音的時候,就會出現失敗的現象,根據列印資訊,我們可以知道,其是在if(pAudioRecord->start()!= android::NO_ERROR)出了問題。也就是說,在android源生的代碼,是不支援兩個程式同時錄音的功能的。

繼續閱讀