Androidフレームワークオーディオサブシステム(16)AudioRecordケース

記事のマスターリンクのこのシリーズ:テーマ別のサブディレクトリのAndroid Frameworkクラスオーディオ・サブシステム


この章の要点の概要と説明:

この章では主に、上記のマインドマップの左上の記録部分に焦点を当てています。それは主にいくつかの録音の基本的な知識を説明し、同時にネイティブテストプログラムを通じてアンドロイドの録音プロセスを理解しています。


1録音の基礎知識

@ 1 PCMとWAVの関係

PCMオーディオデータは元のオーディオデータであり、プレーヤーでは再生できません。サウンドに含まれるチャネル数、サンプリングレートなどを示すヘッダーを追加する必要があります。
他のプレーヤーが再生できるように、PCMオーディオデータをWAV形式に変換します。

@ 2 3録音の重要なパラメータ

  • サンプリングレート:音波が1秒間にサンプリングされる回数。一般的なサンプリングレートは8000、11025、44100です。
  • サンプリング精度:現在、Androidシステムでは16ビットに固定されています。
  • チャネル数:2チャネル(ステレオステレオ、各サンプルポイントは左チャネルと右チャネルの値を記録)またはモノ(モノ)。

2記録テスト手順

関連する3つの主要なドキュメントがあります。

  • AudioRecordTest.cpp:録音プログラム、最後にpcmオーディオ形式のデータを出力します。
  • pcm2wav.cpp:ヘッダー情報をpcm形式で追加し、WAV形式に変換します。
  • Android.mk:構成をコンパイルします。

@ 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;  
    //创建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 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テスト手順の実行ステップ

@ 1録音を開始:

 ./AudioRecordTest 44100 2 my.pcm

@ 2ファイル形式変換(PCMからWAVへ):

./pcm2wav my.pcm 44100 2 my.wav

4遭遇した問題と分析

@ 1 tinyplayはモノラルサウンドを再生できません

プレイする必要がある場合は、他のプレイヤーしか使用できません。

@ 2録音時にデュアルサウンドを使用するのに、再生中に片方の耳だけにサウンドが聞こえるのはなぜですか?モノラル録音を使用すると、再生中に両方の耳に音が聞こえますか?

  • ハードウェアとドライバーはデュアルチャネルですが、MICは1つしか受信しなかったため、録音中にドライバーが取得したデュアルチャネルデータでは、チャネルデータの1つが常に0であり、AudioRecordTestの録音でデュアルが指定されている場合チャネルの場合、取得されたPCMデータのチャネルの1つは常に0であり、再生時に片方の耳だけに音が聞こえます。
  • AudioRecordTestの録音中にMonoが指定されている場合、取得されるPCMデータには、ハードウェアの左チャネルと右チャネルのミックスである1つのチャネルデータのみが含まれます。このミックスはAudioFlingerシステムによって実現されます。再生中にモノラルデータを再生すると、 AudioFlingerシステムは、ハードウェアの左DAC(左チャンネル)とハードウェアの右DAC(右チャンネル)の両方にモノデータを送信するため、両耳で音を聞くことができます
     
元の記事289件を公開 賞賛された47件 30,000回以上の閲覧

おすすめ

転載: blog.csdn.net/vviccc/article/details/105468232