Android Audio (10) - Implementation of Multi-App Simultaneous Recording

Posted by mania on Wed, 29 May 2019 20:12:32 +0200

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

Topics: PHP Android Session git C