1. Writing Recording Program with c++.
1. PCM audio data is the original audio data, which can not be played by the player. It needs to be added a header to indicate how many channels the sound has, how many sampling rates are, and so on. take
PCM audio data is converted to WAV format so that other players can play it.
2. Three parameters should be determined when recording.
(1) Sampling rate: the number of times the sound wave is sampled in one second. Commonly used sampling rates are 8000,11025,22050,32000,44100.
Higher versions of Android should support higher sampling rates.
(2) How many bit s are used for each sample value
The current Android system is fixed at 16 bits
(3) number of vocal tract
Stereo: Stereo, recording left and right channel values at each sampling point
Mono: Mono
3. The tinyplay tool can only play dual-channel audio data.
4. Test procedures
(1)AudioRecordTest.cpp, which is used to do no pcm data
#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; }
(2)pcm2wav.cpp for converting PCM to WAV format
#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; }
(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)
Then use tinyplay to play the generated wav file.
Sound Recording Procedure Reference:
Using AudioRecord to record PCM audio in Android Native C++ layer: https://blog.csdn.net/romantic_energy/article/details/50521970
pcm to wav e reference:
Introduction of PCM and WAV Format and Realization of PCM to WAV by C Language: https://blog.csdn.net/u010011236/article/details/53026127
4. The reason why only one side of the headset plays sound
./AudioRecordTest 44100 2 my.pcm
./pcm2wav my.pcm 44100 2 my.wav
tinyplay my.wav
Only one ear can hear a sound.
./AudioRecordTest 44100 1 my.pcm
./pcm2wav my.pcm 44100 1 my.wav
tinyplay can't play monophonic sounds. It uses other players to play my.wav. Both ears hear sounds.
Why do you use two tones when recording and only one ear when playing?
On the contrary, the recording is done in monophonic mode, and both ears sound when playing?
Answer:
a. Hardware and driver are dual-channel, but we only have one MIC, so one of the two-channel data from driver recording is always 0.
B. If a dual channel is specified for AudioRecordTest recording, then one of the channels in the PCM data is always 0, which will cause only one ear to have a sound when it is played.
C. If a mono channel is specified for AudioRecordTest recording, the PCM data obtained contains only one channel data, which is a mix of left and right channels of hardware.
Audio Flinger is implemented by Audio Flinger system. When playing mono-channel data, Audio Flinger system will send mono-channel data to both hardware Left DAC (left channel) and hardware.
Right DAC, so both ears can hear sound.
II. Recording Framework and Code Flow
1. Playback Thread is MixerThread, and multiple App s correspond to one thread.
2. Native Android recording process
Find the device according to the sound source of App
Find profile (generated by audio_policy.conf)
Find the module according to the profile, which corresponds to a sound card, and load the HAL file corresponding to the sound card.
Call openInput() in the HAL file to open an input channel.
3. As long as App executes set(), a RecordThread() will be created. Multiple Apps may cause concurrent access to the sound card, leading to competitive access.
Ask questions about sound card data.
4. Recording Framework and Code Flow
a. APP creates and sets AudioRecord, specifying the sound source: inputSource, such as AUDIO_SOURCE_MIC, and also specifying parameters such as sampling rate, channel number, format, etc.
B. Audio Policy Manager determines recording equipment based on input Source and other parameters: device
c. AudioFlinger creates a RecordThread, which will later read sound data from the device described above
d. Create a corresponding RecordTrack for APP's AudioRecord within RecordThread, which transfers data through shared memory between APP's AudioRecord and RecordThread's internal RecordTrack
e. RecordThread gets the data from HAL and passes it to the AdioRecord of APP through the internal RecordTrack
Be careful:
In native code, an Audio Record of APP results in the creation of a RecordThread, which may have multiple RecordThreads on a device.
There can only be one RecordThread running at any time, so there can only be one APP recording, not multiple APPs recording at the same time.
3. Modifying code to support multi-APP simultaneous recording
Modify AudioPolicyManager.cpp with the following patches:
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