Android Framework Audio Subsystem (16) Boîtier AudioRecord

Cette série d'articles lien Master: sous-répertoire thématique cadre Android classe Sous - système audio


Résumé et description des points clés de ce chapitre:

Ce chapitre se concentre principalement sur ➕ la partie d'enregistrement supérieure gauche de la carte mentale ci-dessus. Il explique principalement les connaissances de base de certains enregistrements, et en même temps a une compréhension du processus d'enregistrement d'Android via un programme de test natif.


1 Connaissance de base de l'enregistrement

@ 1 Relation entre PCM et WAV

Les données audio PCM sont les données audio d'origine, qui ne peuvent pas être lues par le lecteur. Vous devez y ajouter un en-tête pour indiquer le nombre de canaux du son, la fréquence d'échantillonnage, etc. Convertissez
les données audio PCM au format WAV afin que d'autres joueurs puissent les lire.

@ 2 3 paramètres clés de l'enregistrement

  • Taux d'échantillonnage: nombre de fois que l'onde sonore est échantillonnée par seconde. Les taux d'échantillonnage courants sont 8000, 11025, 44100. . .
  • Précision d'échantillonnage: actuellement fixé à 16 bits sur le système Android.
  • Nombre de canaux: deux canaux (stéréo stéréo, chaque point d'échantillonnage enregistre la valeur des canaux gauche et droit) ou mono (mono).

2 Procédure de test d'enregistrement

Trois documents clés sont impliqués:

  • AudioRecordTest.cpp: programme d'enregistrement, enfin sortie des données au format audio pcm.
  • pcm2wav.cpp: ajoutez des informations d'en-tête au format pcm et convertissez-les au format WAV.
  • Android.mk: Compiler la configuration.

@ 1 AudioRecordTest.cpp, utilisé pour enregistrer des données pcm, mais aucune information d'en-tête n'est ajoutée ici. Le code est le suivant:

#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;  
    //创建AudioRecord
    pAudioRecord  = new android::AudioRecord(); 
    if(NULL == pAudioRecord)
    {
        ALOGE(" create native AudioRecord failed! ");
        goto exit;
    }
    //set操作
    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

#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;

    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;*/
    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 implémentation 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)

3 Étapes d'exécution de la procédure de test

@ 1 Démarrer l'enregistrement:

 ./AudioRecordTest 44100 2 my.pcm

@ 2 Conversion de format de fichier (PCM en WAV):

./pcm2wav my.pcm 44100 2 my.wav

4 Problèmes rencontrés et analyse

@ 1 tinyplay ne peut pas lire le son mono

Si vous devez jouer, vous ne pouvez utiliser que d'autres joueurs.

@ 2 Pourquoi est-ce que j'utilise le double son lors de l'enregistrement et qu'une seule oreille a du son pendant la lecture? Et lorsque vous utilisez l'enregistrement monophonique, les deux oreilles ont du son pendant la lecture?

  • Le matériel et le pilote sont à double canal; mais nous n'avons reçu qu'un seul MIC, donc dans les données à double canal obtenues par le pilote pendant l'enregistrement, l'une des données de canal est toujours 0, et si l'enregistrement AudioRecordTest spécifie double Canal, puis l'un des canaux dans les données PCM obtenues est toujours 0, et il ne fera entendre qu'une seule oreille lors de la lecture.
  • Si Mono est spécifié pendant l'enregistrement AudioRecordTest, les données PCM obtenues ne contiennent qu'une seule donnée de canal, qui est un mélange de canaux matériels gauche et droit. Ce mélange est réalisé par le système AudioFlinger. Le système AudioFlinger enverra des données mono au DAC gauche matériel (canal gauche) et au DAC matériel droit (canal droit), afin que les deux oreilles puissent entendre le son
     
Publié 289 articles originaux · loué 47 · 30 000+ vues

Je suppose que tu aimes

Origine blog.csdn.net/vviccc/article/details/105468232
conseillé
Classement