關於android電話錄音問題的詳細分析
作者:老貓
一直以來都是在網絡上看別人的文章,老老實實的做潛水員,今天一時興起,寫點東西,希望對大家有所幫助,不要再走同樣的彎路。
本文是關於Android下錄音問題的分析,網絡上都說Android錄音時記錄下的語音信號都是混音器的信號。但是都沒有給出詳細說明爲什麼是這樣。
我們知道Android下進行電話錄音的代碼很簡單:
大致流程如下:
recorder = new MediaRecorder();
//這裏mode可以設置爲 VOICE_UPLINK|VOICE_DOWNLINK|VOICE_CALL
recorder.setAudioSource(mode);
recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
recorder.setOutputFile(recFile.getAbsolutePath());
//準備錄音
recorder.prepare();
//啓動錄音
recorder.start();
//停止錄音
recorder.stop();
MediaRecorder.AudioSource中定義了以下常量可以用於recorder.setAudioSource
這裏和電話錄音相關的有3個常量
Voice_call 錄製上行線路和下行線路
Voice_uplink 錄製上行線路,應該是對方的語音
Voice_downlink 錄製下行線路,應該是我方的語音
網絡上關於java層如何調用native代碼的介紹很多,這裏只做簡單介紹。JAVA中MediaRecorder的方法會掉用本地C++代碼,這些代碼編譯後爲libmedia.so,再通過進程間通信機制Binder和MediaServer通信,MediaServer收到請求後,把這些請求轉發給opencore。
以下是Android的媒體庫框架圖,從網絡上下載的。
從上圖可以看出,客戶端調用的本地代碼位於libmedia.so中,媒體服務進程調用的代碼位於libmediaplayerservice.so中。libmediaplayerservice.so再調用底層的libopencoreplayer.so完成具體功能。
以下通過代碼介紹媒體服務進程如何轉發請求到opencore中。關於客戶端mediarecorder如何與媒體服務進程交互請搜索網絡,這方面文章很多,這裏就不多介紹。
總而言之,客戶端的一個mediarecorder對象和服務器端的MediaRecorderClient對象對應,客戶端通過mediarecorder發送的請求,通過進程間通信機制最終都會發送到服務端的MediaRecorderClient類中。我們來看下內部類client的聲明,代碼位於frameworks\base\media\libmediaplayerservice\MediaRecorderClient.h
class MediaRecorderClient : public BnMediaRecorder
{
public:
virtual status_t setCamera(const sp<ICamera>& camera);
virtual status_t setPreviewSurface(const sp<ISurface>& surface);
virtual status_t setVideoSource(int vs);
virtual status_t setAudioSource(int as);
virtual status_t setOutputFormat(int of);
virtual status_t setVideoEncoder(int ve);
virtual status_t setAudioEncoder(int ae);
virtual status_t setOutputFile(const char* path);
virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
virtual status_t setVideoSize(int width, int height);
virtual status_t setVideoFrameRate(int frames_per_second);
virtual status_t setParameters(const String8& params);
virtual status_t setListener(const sp<IMediaPlayerClient>& listener);
virtual status_t prepare();
virtual status_t getMaxAmplitude(int* max);
virtual status_t start();
virtual status_t stop();
virtual status_t reset();
virtual status_t init();
virtual status_t close();
virtual status_t release();
。。。
}
可以看到,大部分客戶端方法在MediaRecorderClient中都有對應方法。這樣當我們調用客戶端的recorder.start();時,最後會調用到MediaRecorderClient類中的start方法。
status_t MediaRecorderClient::start()
{
LOGV("start");
Mutex::Autolock lock(mLock);
if (mRecorder == NULL) {
LOGE("recorder is not initialized");
return NO_INIT;
}
return mRecorder->start(); //轉發給mRecorder
}
//這裏的mRecorder是在MediaRecorderClient構造函數中創建的。
MediaRecorderClient::MediaRecorderClient(const sp<MediaPlayerService>& service, pid_t pid)
{
。。。
#ifndef NO_OPENCORE
{
//創建了PVMediaRecorder用於錄音
mRecorder = new PVMediaRecorder();
}
#else
{
mRecorder = NULL;
}
#endif
mMediaPlayerService = service;
}
其他的調用也是一樣,所有的請求基本都轉發給了PVMediaRecorder,這個PVMediaRecorder就是opencore中的對應的錄音的類。
這樣,我們就直接進入opencore分析,先看看PVMediaRecorder的聲明,代碼位於frameworks\base\include\media\PVMediaRecorder.h,可以看到,客戶端的方法在這裏基本都有對應的方法。
class PVMediaRecorder : public MediaRecorderBase {
public:
PVMediaRecorder();
virtual ~PVMediaRecorder();
virtual status_t init();
virtual status_t setAudioSource(audio_source as);
virtual status_t setVideoSource(video_source vs);
virtual status_t setOutputFormat(output_format of);
virtual status_t setAudioEncoder(audio_encoder ae);
virtual status_t setVideoEncoder(video_encoder ve);
virtual status_t setVideoSize(int width, int height);
virtual status_t setVideoFrameRate(int frames_per_second);
virtual status_t setCamera(const sp<ICamera>& camera);
virtual status_t setPreviewSurface(const sp<ISurface>& surface);
virtual status_t setOutputFile(const char *path);
virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
virtual status_t setParameters(const String8& params);
virtual status_t setListener(const sp<IMediaPlayerClient>& listener);
virtual status_t prepare();
virtual status_t start();
virtual status_t stop();
virtual status_t close();
virtual status_t reset();
virtual status_t getMaxAmplitude(int *max);
private:
status_t doStop();
AuthorDriverWrapper* mAuthorDriverWrapper;
PVMediaRecorder(const PVMediaRecorder &);
PVMediaRecorder &operator=(const PVMediaRecorder &);
};
Opencore是一個第3方的庫,體系比較複雜,關於opencore的詳細資料請參閱android源代碼樹下的external\opencore\doc,網絡上也有這方面資料,不過不全。
總而言之,Opencore提供了一個多媒體開發框架,要使用Opencore進行多媒體應用,開發人員應該在頂層提供包裝接口,在底層提供硬件接口,Opencore提供中間層功能。接收頂層發送的請求,經過處理後,最終交給底層硬件完成任務。在android系統上,頂層和底層代碼都位於目錄external\opencore\android下,其中external\opencore\android\author目錄下是關於錄音部分的代碼。Opencore其他子目錄下是原生代碼。
以下是通過逆向編譯後得到的關於錄音部分的類模型。這裏我們只關注主要部分。
PVMediaRecorder類收到的請求都會轉發給AuthorDriverWrapper類,AuthorDriverWrapper類收到請求後又會轉發給AuthorDriver類,這樣,我們只要關注AuthorDriver類就可以了。
AuthorDriver中定義瞭如下的方法:
void handleInit(author_command *ac);
//##ModelId=4DE0871D000D
void handleSetAudioSource(set_audio_source_command *ac);
//##ModelId=4DE0871D0010
void handleSetCamera(set_camera_command *ac);
//##ModelId=4DE0871D0015
void handleSetVideoSource(set_video_source_command *ac);
//##ModelId=4DE0871D001C
void handleSetOutputFormat(set_output_format_command *ac);
//##ModelId=4DE0871D0021
void handleSetAudioEncoder(set_audio_encoder_command *ac);
//##ModelId=4DE0871D0024
void handleSetVideoEncoder(set_video_encoder_command *ac);
//##ModelId=4DE0871D0029
void handleSetVideoSize(set_video_size_command *ac);
//##ModelId=4DE0871D002E
void handleSetVideoFrameRate(set_video_frame_rate_command *ac);
//##ModelId=4DE0871D0031
void handleSetPreviewSurface(set_preview_surface_command *ac);
//##ModelId=4DE0871D0035
void handleSetOutputFile(set_output_file_command *ac);
//##ModelId=4DE0871D003A
void handleSetParameters(set_parameters_command *ac);
//##ModelId=4DE0871D003D
void handlePrepare(author_command *ac);
//##ModelId=4DE0871D0042
void handleStart(author_command *ac);
//##ModelId=4DE0871D0046
void handleStop(author_command *ac);
//##ModelId=4DE0871D004A
void handleReset(author_command *ac);
//##ModelId=4DE0871D004E
void handleClose(author_command *ac);
//##ModelId=4DE0871D0052
void handleQuit(author_command *ac);
其中每個方法對應於客戶端一個請求的處理,這裏需要注意的是opencore使用了事件調度機制,這種調度機制在opencore的大部分類中都出現,瞭解這種機制有助於我們分析代碼。簡單來說,opencore的大部分類收到一個請求後,會把該請求包裝成1個命令對象,然後添加到命令對象隊列中,在通過調度對這個命令對象進行處理。通常接收請求的方法名和處理請求的方法名字都有對應關係。比如:
status_t PVMediaRecorder::start()
{
LOGV("start");
if (mAuthorDriverWrapper == NULL) {
LOGE("author driver wrapper is not initialized yet");
return UNKNOWN_ERROR;
}
//把請求包裝成命令
author_command *ac = new author_command(AUTHOR_START);
if (ac == NULL) {
LOGE("failed to construct an author command");
return UNKNOWN_ERROR;
}
//調用mAuthorDriverWrapper的enqueueCommand方法
return mAuthorDriverWrapper->enqueueCommand(ac, 0, 0);
}
status_t AuthorDriverWrapper::enqueueCommand(author_command *ac, media_completion_f comp, void *cookie)
{
if (mAuthorDriver) {
//再轉發給mAuthorDriver
return mAuthorDriver->enqueueCommand(ac, comp, cookie);
}
return NO_INIT;
}
status_t AuthorDriver::enqueueCommand(author_command *ac, media_completion_f comp, void *cookie)
{
。。。
//把命令請求添加到命令請求隊列中
mCommandQueue.push_front(ac);
。。。
}
//在opencore調度線程中調用
void AuthorDriver::Run()
{
。。。
//調用handleStart處理客戶的start請求
case AUTHOR_START: handleStart(ac); break;
。。。
}
這樣當客戶端調用recorder.start();時,這個請求通過層層轉發會調用AuthorDriver的handleStart方法,其他請求也一樣,後面就不再列出。
void AuthorDriver::handleStart(author_command *ac)
{
LOGV("handleStart");
int error = 0;
//調用opencore的引擎的start方法
OSCL_TRY(error, mAuthor->Start(ac));
OSCL_FIRST_CATCH_ANY(error, commandFailed(ac));
}
mAuthor成員的初始化在AuthorDriver::authorThread()方法中完成。流程圖如下,圖中Client對象對應於我們的AuthorDriver對象。
PVAuthorEngine類定義在文件external\opencore\engines\author\src\pvauthorengine.h中
其中引擎的start方法定義如下
OSCL_EXPORT_REF PVCommandId PVAuthorEngine::Start(const OsclAny* aContextData)
{
PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,
(0, "PVAuthorEngine::Start: aContextData=0x%x", aContextData));
PVEngineCommand cmd(PVAE_CMD_START, iCommandId, (OsclAny*)aContextData);
Dispatch(cmd);
return iCommandId++;
}
經過調度機制由DoStart方法處理該請求
PVMFStatus PVAuthorEngine::DoStart(PVEngineCommand& aCmd)
{
PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVAuthorEngine::DoStart"));
OSCL_UNUSED_ARG(aCmd);
if (GetPVAEState() != PVAE_STATE_INITIALIZED)
{
return PVMFErrInvalidState;
}
iNodeUtil.Start(iComposerNodes);
if (iEncoderNodes.size() > 0)
iNodeUtil.Start(iEncoderNodes);
//調用PVAuthorEngineNodeUtility的start方法完成請求
iNodeUtil.Start(iDataSourceNodes);
return PVMFPending;
}
PVMFStatus PVAuthorEngineNodeUtility::Start(const PVAENodeContainerVector& aNodes, OsclAny* aContext)
{
。。。
PVAENodeUtilCmd cmd;
PVMFStatus status = cmd.Construct(PVAENU_CMD_START, aNodes, aContext);
。。。又是調度機制,由DoStart處理
return AddCmdToQueue(cmd);
}
PVMFStatus PVAuthorEngineNodeUtility::DoStart(const PVAENodeUtilCmd& aCmd)
{
。。。
for (uint32 i = 0; i < aCmd.iNodes.size(); i++)
{
nodeContainer = aCmd.iNodes[i];
nodeContainer->iNode->Start(nodeContainer->iSessionId, aCmd.iContext);
}
);
。。。。
}
注意到這裏面調用了nodeContainer->iNode->Start(nodeContainer->iSessionId, aCmd.iContext);
這個iNode是在調用start函數前面的函數時保存到opencore的引擎中的,具體分析過程比較複雜,這裏就不詳細列出。
總而言之,inode指向PvmfMediaInputNode對象,代碼位於external\opencore\nodes\pvmediainputnode\src\pvmf_media_input_node.h
OSCL_EXPORT_REF PVMFCommandId PvmfMediaInputNode::Start(PVMFSessionId s, const OsclAny* aContext)
{
PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,
(0, "PvmfMediaInputNode::Start() called"));
PvmfMediaInputNodeCmd cmd;
cmd.PvmfMediaInputNodeCmdBase::Construct(s, PVMF_GENERIC_NODE_START, aContext);
return QueueCommandL(cmd);
}
通過調度機制又調用了DoStart方法
PVMFStatus PvmfMediaInputNode::DoStart(PvmfMediaInputNodeCmd& aCmd)
{
。。。
//Start the MIO
PVMFStatus status = SendMioRequest(aCmd, EStart);
。。。
}
////////////////////////////////////////////////////////////////////////////
PVMFStatus PvmfMediaInputNode::SendMioRequest(PvmfMediaInputNodeCmd& aCmd, EMioRequest aRequest)
{
。。。
case EStart:
{
OSCL_TRY(err, iMediaIOCmdId = iMediaIOControl->Start(););
。。。
}
調用iMediaIOControl的start方法,這個iMediaIOControl成員是在創建該節點時候傳遞近來的,具體分析過程就不說了,太長。總之,iMediaIOControl指向AndroidAudioInput類,還記得我們說過,使用opencore要提供底層接口嗎,這個就是android提供的底層接口,代碼位於external\opencore\android\author\android_audio_input.h
PVMFCommandId AndroidAudioInput::Start(const OsclAny* aContext)
{
LOGV("Start");
if(iState != STATE_INITIALIZED && iState != STATE_PAUSED)
{
LOGE("Start: Invalid state (%d)", iState);
OSCL_LEAVE(OsclErrInvalidState);
return -1;
}
return AddCmdToQueue(AI_CMD_START, aContext);
}
通過調度機制由DoStart完成
////////////////////////////////////////////////////////////////////////////
PVMFStatus AndroidAudioInput::DoStart()
{
OsclThread AudioInput_Thread;
//創建一個線程,線程入口函數start_audin_thread_func
OsclProcStatus::eOsclProcError ret = AudioInput_Thread.Create(
(TOsclThreadFuncPtr)start_audin_thread_func, 0,
(TOsclThreadFuncArg)this, Start_on_creation);
。。。
int AndroidAudioInput::start_audin_thread_func(TOsclThreadFuncArg arg)
{
prctl(PR_SET_NAME, (unsigned long) "audio in", 0, 0, 0);
sp<AndroidAudioInput> obj = (AndroidAudioInput *)arg;
//調用audin_thread_func函數
return obj->audin_thread_func();
}
//注意這裏創建了一個AudioRecord來完成實際的錄音底層工作
int AndroidAudioInput::audin_thread_func() {
// setup audio record session
//最後調用的是AudioRecord類完成音頻錄製
LOGV("create AudioRecord %p", this);
AudioRecord
* record = new AudioRecord(
iAudioSource, iAudioSamplingRate,
android::AudioSystem::PCM_16_BIT,
(iAudioNumChannels > 1) ? AudioSystem::CHANNEL_IN_STEREO : AudioSystem::CHANNEL_IN_MONO,
4*kBufferSize/iAudioNumChannels/sizeof(int16), flags);
。。。。。
好了,繞了一整圈,我們知道opencore實際上並不負責底層錄音的設置,最終工作是由AudioRecord來完成的,那我們來分析AudioRecord是如何完成錄音工作的。
AudioRecord類聲明在文件frameworks\base\media\libmedia\AudioRecord.h中,是audioflinger的一部分。先看下構造函數
AudioRecord::AudioRecord(
int inputSource,
uint32_t sampleRate,
int format,
uint32_t channels,
int frameCount,
uint32_t flags,
callback_t cbf,
void* user,
int notificationFrames)
: mStatus(NO_INIT)
{
//調用set方法
mStatus = set(inputSource, sampleRate, format, channels,
frameCount, flags, cbf, user, notificationFrames);
}
status_t AudioRecord::set(
int inputSource,
uint32_t sampleRate,
int format,
uint32_t channels,
int frameCount,
uint32_t flags,
callback_t cbf,
void* user,
int notificationFrames,
bool threadCanCallJava)
{
// input實際上是錄音線程句柄,調用AudioSystem::getInput
audio_io_handle_t input = AudioSystem::getInput(inputSource,
sampleRate, format, channels, (AudioSystem::audio_in_acoustics)flags);
}
10、audiosystem.cpp
audio_io_handle_t AudioSystem::getInput(int inputSource,
uint32_t samplingRate,
uint32_t format,
uint32_t channels,
audio_in_acoustics acoustics)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
if (aps == 0) return 0;
return aps->getInput(inputSource, samplingRate, format, channels, acoustics);
}
調用的是AudioPolicyService的getInput,代碼位於frameworks\base\libs\audioflinger\AudioPolicyService.cpp
audio_io_handle_t AudioPolicyService::getInput(int inputSource,
uint32_t samplingRate,
uint32_t format,
uint32_t channels,
AudioSystem::audio_in_acoustics acoustics)
{
if (mpPolicyManager == NULL) {
return 0;
}
Mutex::Autolock _l(mLock);
//調用audioPolicyManagerbase的getinput
return mpPolicyManager->getInput(inputSource, samplingRate, format, channels, acoustics);
}
audio_io_handle_t AudioPolicyManagerBase::getInput(int inputSource,
uint32_t samplingRate,
uint32_t format,
uint32_t channels,
AudioSystem::audio_in_acoustics acoustics)
{
audio_io_handle_t input = 0;
// case AUDIO_SOURCE_VOICE_UPLINK:case AUDIO_SOURCE_VOICE_DOWNLINK:case AUDIO_SOURCE_VOICE_CALL:device = AudioSystem::DEVICE_IN_VOICE_CALL;
// 對於電話錄音,返回的是DEVICE_IN_VOICE_CALL
uint32_t device = getDeviceForInputSource(inputSource);
// 設置選擇的錄音通道,如果是電話錄音,設置相應得通道標誌
switch(inputSource) {
case AUDIO_SOURCE_VOICE_UPLINK:
channels = AudioSystem::CHANNEL_IN_VOICE_UPLINK;
break;
case AUDIO_SOURCE_VOICE_DOWNLINK:
channels = AudioSystem::CHANNEL_IN_VOICE_DNLINK;
break;
case AUDIO_SOURCE_VOICE_CALL:
channels = (AudioSystem::CHANNEL_IN_VOICE_UPLINK | AudioSystem::CHANNEL_IN_VOICE_DNLINK);
break;
default:
break;
}
//調用AudioPolicyService的openInput
input = mpClientInterface->openInput(&inputDesc->mDevice,
&inputDesc->mSamplingRate,
&inputDesc->mFormat,
&inputDesc->mChannels,
inputDesc->mAcoustics);
return input;
}
audio_io_handle_t AudioPolicyService::openInput(uint32_t *pDevices,
uint32_t *pSamplingRate,
uint32_t *pFormat,
uint32_t *pChannels,
uint32_t acoustics)
{
sp<IAudioFlinger> af = AudioSystem::get_audio_flinger();
if (af == 0) {
LOGW("openInput() could not get AudioFlinger");
return 0;
}
//調用AudioFlinger的openInput
return af->openInput(pDevices, pSamplingRate, (uint32_t *)pFormat, pChannels, acoustics);
}
AudioFlinger類定義在frameworks\base\libs\audioflinger\AudioFlinger.cpp中
int AudioFlinger::openInput(uint32_t *pDevices,
uint32_t *pSamplingRate,
uint32_t *pFormat,
uint32_t *pChannels,
uint32_t acoustics)
//打開錄音輸入流,對於電話錄音參數,這裏調用肯定失敗
AudioStreamIn *input = mAudioHardware->openInputStream(*pDevices,
(int *)&format,
&channels,
&samplingRate,
&status,
(AudioSystem::audio_in_acoustics)acoustics);
//錯誤的話會返回,使用單通道模式重新打開
input = mAudioHardware->openInputStream(*pDevices,
(int *)&format,
&channels,
&samplingRate,
&status,
(AudioSystem::audio_in_acoustics)acoustics);
上面的mAudioHardware指向AudioHardware
AudioStreamIn* AudioHardware::openInputStream(
uint32_t devices, int *format, uint32_t *channels, uint32_t *sampleRate, status_t *status, AudioSystem::audio_in_acoustics acoustic_flags)
{
AudioStreamInMSM72xx* in = new AudioStreamInMSM72xx();
//對硬件進行設置
status_t lStatus = in->set(this, devices, format, channels, sampleRate, acoustic_flags);
status_t AudioHardware::AudioStreamInMSM72xx::set(
AudioHardware* hw, uint32_t devices, int *pFormat, uint32_t *pChannels, uint32_t *pRate,
AudioSystem::audio_in_acoustics acoustic_flags)
{
。。。。
//不支持AUDIO_SOURCE_VOICE_UPLINK | AUDIO_SOURCE_VOICE_DOWNLINK
//什麼樣的音頻硬件支持?
////如果通道不是CHANNEL_IN_MONO或者CHANNEL_IN_STEREO,把通道設置成單通道
//返回錯誤,還記得在AudioFlinger::openInput中檢測到錯誤會重新調用嗎?
//重新調用時使用AUDIO_HW_IN_CHANNELS,
// #define AUDIO_HW_IN_CHANNELS (AudioSystem::CHANNEL_IN_MONO)
//實際上就是單通道,然後在af的openinput中重新調用,就成功了
if (pChannels == 0 || (*pChannels != AudioSystem::CHANNEL_IN_MONO &&
*pChannels != AudioSystem::CHANNEL_IN_STEREO)) {
*pChannels = AUDIO_HW_IN_CHANNELS;
return BAD_VALUE;
}
分析到這裏終於知道了,當我們設置錄音方式爲
Voice_call 錄製上行線路和下行線路
Voice_uplink 錄製上行線路,應該是對方的語音
Voice_downlink 錄製下行線路,應該是我方的語音
第1次打開設備時肯定失敗,失敗後android把通道標誌設置爲CHANNEL_IN_MONO,然後調用成功,實際上此時使用的應該是混音器錄音。
希望大家不要再犯相同的錯誤,謝謝!!