關於android電話錄音問題的詳細分析

關於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);

}

10audiosystem.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);

//調用audioPolicyManagerbasegetinput

    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;

    }

//調用AudioPolicyServiceopenInput

    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;

    }

//調用AudioFlingeropenInput

    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,然後調用成功,實際上此時使用的應該是混音器錄音。

希望大家不要再犯相同的錯誤,謝謝!!

    

參考文檔:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章