記事のマスターリンクのこのシリーズ:テーマ別のサブディレクトリの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(右チャンネル)の両方にモノデータを送信するため、両耳で音を聞くことができます