Android音频框架之三 用户录音启动流程源码走读 startRecord

前言

此篇是对《Android音频框架之一 详解audioPolicy流程及HAL驱动加载》 和《Android音频框架之二 用户录音启动流程源码走读》的延续,
此系列博文是记录在Android7.1系统即以后版本实现内录音功能。
当用户使用 AudioRecord 录音 API 创建 new AudioRecord 后,会创建文件流把 AudioRecord 的音频流写入到文件流或网络流中,
调用的方法时 audioRecord.read() 方法,把数据写入到文件流中,源码如下

public class RecorderDefine {

    private static String TAG = "Debug_dump_info:";
    AudioRecord mRecord = null;
    AudioManager mAudioManager = null;
    boolean mReqStop = false;
    private static RecorderDefine mRecorderDefine = null;
    Thread mRecorderThread = null;
    private Method getServiceMethod;

    public  RecorderDefine(){
        init();
    }

    public static RecorderDefine getInstance(){
        if(mRecorderDefine == null ){
            mRecorderDefine = new RecorderDefine();
        }
        Log.d(TAG,"Create RecorderDefine..");
        return mRecorderDefine;
    }

    public void StartRecorder(){

        if(mRecorderThread == null){
            mRecorderThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    recordAndPlay();
                }
            });
        }

        mRecorderThread.start();
    }

    private final int kSampleRate = 44100;
    private final int kChannelMode = AudioFormat.CHANNEL_IN_STEREO;
    private final int kEncodeFormat = AudioFormat.ENCODING_PCM_16BIT;

    private void init() {
        int minBufferSize = AudioRecord.getMinBufferSize(kSampleRate, kChannelMode,
                kEncodeFormat);
        mRecord = new AudioRecord(MediaRecorder.AudioSource.REMOTE_SUBMIX,
                kSampleRate, kChannelMode, kEncodeFormat, minBufferSize * 2);  //>REMOTE_SUBMIX
        Log.d(TAG,"Create AudiRecord ...");
    }

    private final int kFrameSize = 2048;
    private String filePath = "/voice-sub.pcm";
    private String outfile = "/voice-sub.wav";

    private void recordAndPlay() {
        FileOutputStream os = null;
        mRecord.startRecording();
        byte[] buffer = new byte[kFrameSize];
        int num = 0;

        try {
            os = new FileOutputStream(Environment.getExternalStorageDirectory().getPath()+filePath);
            while (!mReqStop) {
                num = mRecord.read(buffer, 0, kFrameSize);						//> 读取 AudioRecord 中音频流
                Log.d(TAG, "buffer = " + buffer.toString() + ", num = " + num);
                os.write(buffer, 0, num);										//> 写入文件流中
            }
            Log.d(TAG, "exit loop");
            os.close();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e(TAG, "Dump PCM to file failed");
        }

        mRecord.stop();
        mRecord.release();
        mRecord = null;
        mReqStop = false;
        PcmToWavUtil pcmToWavUtil = new PcmToWavUtil(kSampleRate,2,kEncodeFormat);//> 把音频 pcm裸流转换为 wav 格式
        pcmToWavUtil.pcmToWav(Environment.getExternalStorageDirectory().getPath()+filePath,
                Environment.getExternalStorageDirectory().getPath()+outfile);
        Log.d(TAG, "out wav format file:" + outfile);
    }

    public void StopRecord(){
        mReqStop = true;
        Log.d(TAG, "onClick StopRecord");
    }

}

此 wav 格式音频流就可以通过 VLC 播放器进行播放验证,下面会分享录制后的文件。

环境说明

源码:android7.1 版本,硬件平台 RK3288-box。

安卓系统录制时、内核程序流程是什么呢?本篇对录制启动、录制和结束代码进行走读。

step 1:

上篇中已经走读 AudioRecord init过程,录制的startInput是在前面基础上,也就是说 init 过程已经获取设备相关信息,此部分可是创建设备、并启动 startInput 流。
用户在App中录音时,调用的 JNI 接口如下:

@ frameworks/base/core/jni/android_media_AudioRecord.cpp
static const JNINativeMethod gMethods[] = {
    // name,               signature,  funcPtr
    {"native_start",         "(II)I",    (void *)android_media_AudioRecord_start},   //> 启动录音JNI 接口
    {"native_stop",          "()V",    (void *)android_media_AudioRecord_stop},      //> 停止录音 JNI 接口
    {"native_setup",         "(Ljava/lang/Object;Ljava/lang/Object;[IIIII[ILjava/lang/String;J)I",
                                      (void *)android_media_AudioRecord_setup},      //> 设置 AudioRecord 参数
}

接口源码如下:

static jint
android_media_AudioRecord_start(JNIEnv *env, jobject thiz, jint event, jint triggerSession)
{
    sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz);
    if (lpRecorder == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return (jint) AUDIO_JAVA_ERROR;
    }

    return nativeToJavaStatus(
            lpRecorder->start((AudioSystem::sync_event_t)event, (audio_session_t) triggerSession));
}

static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz)
{
    Mutex::Autolock l(sLock);
    AudioRecord* const ar =
            (AudioRecord*)env->GetLongField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj);
    return sp<AudioRecord>(ar);
}

获取 lpRecorder 是谁呢? 就是程序在 init 时创建的 Audio client 端实例,通过此实例方法发送 同步事件到 系统 AudioRecord 服务,系统服务器启动录音。
调用的 start() 方法,就是 AudioRecord::start() 方法,见 step 2 中内容。下面是启动audioRecord的打印信息。

03-14 15:02:34.005  2239  2239 D AudioRecord: Debug_dump_info: android::AudioRecord::AudioRecord(const android::String16 &) : 73 , create AudioRecord ... 
03-14 15:02:34.005  2239  2239 D AudioRecord-JNI: Debug_dump_info: android_media_AudioRecord_setup,267 opPackageNameStr:com.dds.webrtc    //> 调试 Demo 包名称
03-14 15:02:34.005  2239  2239 D AudioRecord-JNI: Debug_dump_info: android_media_AudioRecord_setup, 279, AudioRecord_setup for source=8 tags= flags=00000000
03-14 15:02:34.005  2239  2239 D AudioRecord: Debug_dump_info line:154 inputSource 8, sampleRate 44100, format 0x1, channelMask 0xc, frameCount 4096, notificationFrames 0, sessionId 0, transferType 0, flags 0, opPackageName com.dds.webrtc uid -1, pid -1
03-14 15:02:34.005  2239  2239 D AudioRecord: Debug_dump_info line:194 Building AudioRecord with attributes: source=8 flags=0x0 tags=[]
03-14 15:02:34.005  2239  2239 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::set(audio_source_t, uint32_t, audio_format_t, audio_channel_mask_t, size_t, callback_t, void *, uint32_t, bool, audio_session_t, android::AudioRecord::transfer_type, audio_input_flags_t, int, pid_t, const audio_attributes_t *),237 mSessionId 17

03-14 11:06:59.373  1627  1627 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::openRecord_l(const Modulo<uint32_t> &, const android::String16 &) , 718 
03-14 11:06:59.373  1627  1627 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::set(audio_source_t, uint32_t, audio_format_t, audio_channel_mask_t, size_t, callback_t, void *, uint32_t, bool, audio_session_t, android::AudioRecord::transfer_type, audio_input_flags_t, int, pid_t, const audio_attributes_t *),263 set() runStatus:0 
03-14 11:06:59.373  1627  1627 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::set(audio_source_t, uint32_t, audio_format_t, audio_channel_mask_t, size_t, callback_t, void *, uint32_t, bool, audio_session_t, android::AudioRecord::transfer_type, audio_input_flags_t, int, pid_t, const audio_attributes_t *),287 set() run over
03-14 11:06:59.373  1627  1627 D AudioRecord-JNI: Debug_dump_info: android_media_AudioRecord_setup,359                                   //> _setup接口 JNI 调用

step 2: AudioRecord.start() 函数

AudioRecord::start() 源码如下

@ frameworks/av/media/libmedia/AudioRecord.cpp
status_t AudioRecord::start(AudioSystem::sync_event_t event, audio_session_t triggerSession)
{
    ALOGD("Debug_dump_info: %d sync event %d trigger session %d", __LINE__, event, triggerSession);

    AutoMutex lock(mLock);
    if (mActive) {
        return NO_ERROR;
    }

    mMarkerReached = false;
    mActive = true;

    status_t status = NO_ERROR;
    if (!(flags & CBLK_INVALID)) {
        status = mAudioRecord->start(event, triggerSession);    //> 把事件和 Session发送至 服务端
        if (status == DEAD_OBJECT) {
            flags |= CBLK_INVALID;
        }
    }

    if (status != NO_ERROR) {
        mActive = false;
        ALOGE("start() status %d", status);
    } else {
        sp<AudioRecordThread> t = mAudioRecordThread;
        if (t != 0) {
            t->resume();
        } else {
            mPreviousPriority = getpriority(PRIO_PROCESS, 0);
            get_sched_policy(0, &mPreviousSchedulingGroup);
            androidSetThreadPriority(0, ANDROID_PRIORITY_AUDIO);
        }
    }

    return status;
}

系统调试日志,此 Session id 就是在init阶段建立的,服务端根据此 id 启动Input流,由此引发对 TinyAlsaHAL 层函数的调用。

3-14 17:53:49.592 11539 11655 D AudioRecord: Debug_dump_info: 295 sync event 0 trigger session 0 //> 发送 事件提示信息
03-14 17:53:49.596   229   229 D APM_AudioPolicyManager: Debug_dump_info: virtual status_t android::AudioPolicyManager::startInput(audio_io_handle_t, audio_session_t),1641 startInput() input 30
03-14 17:53:49.596   229   229 D APM_AudioPolicyManager: Debug_dump_info: audio_devices_t android::AudioPolicyManager::getDeviceAndMixForInputSource(audio_source_t, android::AudioMix **),5045 
03-14 17:53:49.596   229   229 D APM_AudioPolicyManager: Debug_dump_info: audio_devices_t android::AudioPolicyManager::getDeviceAndMixForInputSource(audio_source_t, android::AudioMix **),5049 
03-14 17:53:49.597   229   229 D APM_AudioPolicyManager: Debug_dump_info: virtual audio_devices_t android::AudioPolicyManager::getDeviceForInputSource(audio_source_t) , 5058 mInputRoutes.size:1 
03-14 17:53:49.597   229   229 D APM_AudioPolicyManager: Debug_dump_info: virtual audio_devices_t android::AudioPolicyManager::getDeviceForInputSource(audio_source_t) , 5066 routeIndex:1 
03-14 17:53:49.597   229   229 D APM_AudioPolicyManager: Debug_dump_info: status_t android::AudioPolicyManager::setInputDevice(audio_io_handle_t, audio_devices_t, bool, audio_patch_handle_t *) , 4924 
03-14 17:53:49.598   229   289 D AudioFlinger::PatchPanel: Debug_dump_info: status_t android::AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *, audio_patch_handle_t *) , 164 
03-14 17:53:49.598   229   289 D AudioFlinger::PatchPanel: Debug_dump_info: status_t android::AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *, audio_patch_handle_t *) , 245 
03-14 17:53:49.599   229 11558 D AudioParameter: Debug_dump_info: android::AudioParameter::AudioParameter(const android::String8 &) : 64 , create AudioParameter ... 
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info: in_set_parameters: kvpairs = a2dp_sink_address=0;input_source=8;routing=-2147483392
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters : 1994 source: 8 
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters : 2000 in->device:256 
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters , 2012 Device:256 
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters , 2018 
03-14 17:53:49.599   229 11558 D AudioHardwareTiny: Debug_dump_info: in_set_parameters: exit: status(0)
03-14 17:53:49.600   229   289 D AudioFlinger::PatchPanel: Debug_dump_info: status_t android::AudioFlinger::PatchPanel::createAudioPatch(const struct audio_patch *, audio_patch_handle_t *) , 343 
03-14 17:53:49.600   229   229 D APM_AudioPolicyManager: Debug_dump_info: setInputDevice() createAudioPatch returned 0 patchHandle 44
03-14 17:53:49.600   229   229 D APM_AudioPolicyManager: Debug_dump_info:  found 1 profiles, 0 outputs
03-14 17:53:49.600   229   229 D APM_AudioPolicyManager: Debug_dump_info: opening output for device 00008000 with params 0 profile 0xb0f13300
03-14 17:53:49.601   229   229 D AudioHardwareTiny: Debug_dump_info: adev_open_output_stream devices = 0x8000, flags = 0, samplerate = 48000
03-14 17:53:49.601   229   229 D AudioHardwareTiny: Debug_dump_info: adev_open_output_stream : 2397  
03-14 17:53:49.601   229   229 D AudioHardwareTiny: Debug_dump_info: out->config.rate = 44100, out->config.channels = 2 out->config.format = 0,out->config.flag = 0

step 3: 打开输入流使用的 PCM_CARDS 和 PCM_DEVICE 设备

此源码在RK3288平台上实现、内容如下:

@ hardware/rockchip/audio/tinyalsa_hal/audio_hw.c 
static int start_input_stream(struct stream_in *in)
{
    struct audio_device *adev = in->dev;
    int  ret = 0;
    //ALOGV("Debug_dump_info: %s : %d , in->device: 0x%04x \n ", __func__, __LINE__, in->device);
    ALOGD("Debug_dump_info: %s : %d enter process", __func__, __LINE__ );
    in_dump(in, 0);
    route_pcm_open(getRouteFromDevice(in->device | AUDIO_DEVICE_BIT_IN));

    if(in->input_source == 0x08){ //> Remote_submix type
        ALOGD("Debug_dump_info: %s : %d ,PCM_CARD:%d PCM_DEVICE:%d PCM_IN:%d ", __func__, __LINE__, PCM_CARD, PCM_DEVICE_SCO, PCM_IN);
        in->pcm = pcm_open(PCM_CARD, PCM_DEVICE_SCO, PCM_IN, in->config);
    }else{
        ALOGD("Debug_dump_info: %s : %d ,PCM_CARD:%d PCM_DEVICE:%d PCM_IN:%d ", __func__, __LINE__, PCM_CARD, PCM_DEVICE, PCM_IN);
        in->pcm = pcm_open(PCM_CARD, PCM_DEVICE, PCM_IN, in->config);
    }

    if (in->pcm && !pcm_is_ready(in->pcm)) {
        ALOGE("Debug_dump_info: pcm_open() failed: %s", pcm_get_error(in->pcm));
        pcm_close(in->pcm);
        return -ENOMEM;
    }

    /* if no supported sample rate is available, use the resampler */
    if (in->resampler)
        in->resampler->reset(in->resampler);

    in->frames_in = 0;
    adev->input_source = in->input_source;
    adev->in_device = in->device;
    adev->in_channel_mask = in->channel_mask;
    ALOGD("Debug_dump_info: %s : %d , sourceType: %4x \n ", __func__, __LINE__, in->input_source);
    if (in->device & AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET)
        start_bt_sco(adev);

    /* initialize volume ramp */
    in->ramp_frames = (CAPTURE_START_RAMP_MS * in->requested_rate) / 1000;
    in->ramp_step = (uint16_t)(USHRT_MAX / in->ramp_frames);
    in->ramp_vol = 0;;
    ALOGD("Debug_dump_info: %s : %d exit process", __func__, __LINE__ );
    return 0;
}

笔者在源码中修改对 Remote_submix 输入源类型支持,对应的声卡和PCM_DEVICE如源码中描述,读者如不是很了解对应关系,
请参考《ubuntu 20 使用命令行 snd-aloop 实现内录音、录制音乐播放器的音频》 博文内容,snd-aloop声卡驱动配置文件中,
已经指定此 PCM_DEVICE 为 1 时,通过 snd-aloop驱动获取的音频数据,就是系统播放音频内容。
调试日志内容如下:

03-14 15:03:29.299   230  1409 D APM_AudioPolicyManager: Debug_dump_info: setInputDevice() createAudioPatch returned 0 patchHandle 44
03-14 15:03:29.300   230  1409 D APM_AudioPolicyManager: Debug_dump_info:  found 1 profiles, 0 outputs
03-14 15:03:29.300   230  1409 D APM_AudioPolicyManager: Debug_dump_info: opening output for device 00008000 with params 0 profile 0xac813300
03-14 15:03:29.300   230  1409 D AudioHardwareTiny: Debug_dump_info: adev_open_output_stream devices = 0x8000, flags = 0, samplerate = 48000
03-14 15:03:29.300   230  1409 D AudioHardwareTiny: Debug_dump_info: adev_open_output_stream : 2397  
03-14 15:03:29.300   230  1409 D AudioHardwareTiny: Debug_dump_info: out->config.rate = 44100, out->config.channels = 2 out->config.format = 0,out->config.flag = 0
03-14 15:03:29.302   230  1409 D APM_AudioPolicyManager: Debug_dump_info: AudioPolicyManager::startInput() input source = 8
03-14 15:03:29.304   230  2278 D AudioHardwareTiny: Debug_dump_info: start_input_stream : 826 enter process
03-14 15:03:29.305   230  2278 D AudioHardwareTiny: Debug_dump_info: getInputRouteFromDevice :361  device:80000100
03-14 15:03:29.305   230  2278 D alsa_route: Debug_dump_info: enter route_pcm_open() route: 25
03-14 15:03:29.305   230  2278 D alsa_route: Debug_dump_info:route 25 error for codec or hdmi!
03-14 15:03:29.305   230  2278 D alsa_route: Debug_dump_info: route_pcm_open exit
03-14 15:03:29.305   230  2278 D AudioHardwareTiny: Debug_dump_info: start_input_stream : 876 ,PCM_CARD:0 PCM_DEVICE:1 PCM_IN:268435456  /> 打开的 SND 和 PCM_DEVICE
03-14 15:03:29.305   230  2278 D AudioHardwareTiny: Debug_dump_info: start_input_stream : 898 , sourceType:    8 
03-14 15:03:29.305   230  2278 D AudioHardwareTiny: Debug_dump_info: start_input_stream : 906 exit process

声卡设备打开完成,解下来就是读取 AudioRecord 的音频数据。

step 4:

读取声卡音频数据源码如下;

@ hardware/rockchip/audio/tinyalsa_hal/audio_hw.c 
static ssize_t in_read(struct audio_stream_in *stream, void* buffer,
                       size_t bytes)
{
    int ret = 0;
    struct stream_in *in = (struct stream_in *)stream;
    struct audio_device *adev = in->dev;
    size_t frames_rq = bytes / audio_stream_in_frame_size(stream);

    /*
     * acquiring hw device mutex systematically is useful if a low
     * priority thread is waiting on the input stream mutex - e.g.
     * executing in_set_parameters() while holding the hw device
     * mutex
     */
    pthread_mutex_lock(&in->lock);
    if (in->standby) {
        pthread_mutex_lock(&adev->lock);
        ret = start_input_stream(in);
        pthread_mutex_unlock(&adev->lock);
        if (ret < 0)
            goto exit;
        in->standby = false;
#ifdef AUDIO_3A
        if (adev->voice_api != NULL) {
            adev->voice_api->start();
        }
#endif
    }
    /*if (in->num_preprocessors != 0)
        ret = process_frames(in, buffer, frames_rq);
      else */
    ALOGD("Debug_dump_info: %s: line:%d ", __FUNCTION__, __LINE__);
    ret = read_frames(in, buffer, frames_rq);
    if (ret > 0)
        ret = 0;

#ifdef AUDIO_3A
    do {
        if (adev->voice_api != NULL) {
            int ret  = 0;
            ret = adev->voice_api->quueCaputureBuffer(buffer, bytes);
            if (ret < 0) break;
            ret = adev->voice_api->getCapureBuffer(buffer, bytes);
            if (ret < 0) memset(buffer, 0x00, bytes);
        }
    } while (0);
    ALOGD("Debug_dump_info: %s: line:%d ", __FUNCTION__, __LINE__);
#endif

    //if (in->ramp_frames > 0)
    //    in_apply_ramp(in, buffer, frames_rq);

    /*
     * Instead of writing zeroes here, we could trust the hardware
     * to always provide zeroes when muted.
     */
    //if (ret == 0 && adev->mic_mute)
    //    memset(buffer, 0, bytes);
#ifdef SPEEX_DENOISE_ENABLE
    if(!adev->mic_mute && ret== 0) {
        int index = 0;
        int startPos = 0;
        spx_int16_t* data = (spx_int16_t*) buffer;

        int channel_count = audio_channel_count_from_out_mask(in->channel_mask);
        int curFrameSize = bytes/(channel_count*sizeof(int16_t));
        long ch;
        if(curFrameSize != 2*in->mSpeexFrameSize)
            ALOGD("the current request have some error mSpeexFrameSize %d bytes %d ",in->mSpeexFrameSize,bytes);

        ALOGD("Debug_dump_info: %s line %d , startPos: %d ", __FUNCTION__, __LINE__, startPos);
        while(curFrameSize >= startPos+in->mSpeexFrameSize) {

            for(index = startPos; index< startPos +in->mSpeexFrameSize ; index++ )
                in->mSpeexPcmIn[index-startPos] = data[index*channel_count]/2 + data[index*channel_count+1]/2;

            speex_preprocess_run(in->mSpeexState,in->mSpeexPcmIn);
#ifndef TARGET_RK2928
            for(ch = 0 ; ch < channel_count; ch++)
                for(index = startPos; index< startPos + in->mSpeexFrameSize ; index++ ) {
                    data[index*channel_count+ch] = in->mSpeexPcmIn[index-startPos];
                }
#else
            for(index = startPos; index< startPos + in->mSpeexFrameSize ; index++ ) {
                int tmp = (int)in->mSpeexPcmIn[index-startPos]+ in->mSpeexPcmIn[index-startPos]/2;
                data[index*channel_count+0] = tmp > 32767 ? 32767 : (tmp < -32768 ? -32768 : tmp);
            }
            for(int ch = 1 ; ch < channel_count; ch++)
                for(index = startPos; index< startPos + in->mSpeexFrameSize ; index++ ) {
                    data[index*channel_count+ch] = data[index*channel_count+0];
                }
#endif
            startPos += in->mSpeexFrameSize;
        }
    }
#endif

exit:
    if (ret < 0)
        usleep(bytes * 1000000 / audio_stream_in_frame_size(stream) /
               in_get_sample_rate(&stream->common));

    pthread_mutex_unlock(&in->lock);
    return bytes;
}

此源码不做解释,看下面调试打印日志内容吧。

03-14 15:03:29.305   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read: line:2128 
03-14 15:03:29.308  2239  2277 D AudioRecord: Debug_dump_info: virtual bool android::AudioRecord::AudioRecordThread::threadLoop() , 1268 
03-14 15:03:29.308  2239  2277 D AudioRecord: Debug_dump_info: nsecs_t android::AudioRecord::processAudioBuffer() , 926 
03-14 15:03:29.308  2239  2277 D AudioRecord: Debug_dump_info: virtual bool android::AudioRecord::AudioRecordThread::threadLoop() , 1268 
03-14 15:03:29.314  2239  2951 D AudioRecord: Debug_dump_info: ssize_t android::AudioRecord::read(void *, size_t, bool) , 881 
03-14 15:03:29.328   230  2278 D AudioHardwareTiny: Debug_dump_info: frames_wr:0,buf.frame_count:1024,frame_size:4====
03-14 15:03:29.328   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read line 2167 , startPos: 0 
03-14 15:03:29.329   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read: line:2128 
03-14 15:03:29.329  2239  2951 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::obtainBuffer(android::AudioRecord::Buffer *, const struct timespec *, struct timespec *, size_t *) , 838 frameCount:512 
03-14 15:03:29.329  2239  2951 D Debug_dump_info:: buffer = [B@7be48d1, num = 2048
03-14 15:03:29.329  2239  2951 D AudioRecord: Debug_dump_info: ssize_t android::AudioRecord::read(void *, size_t, bool) , 881 
03-14 15:03:29.329  2239  2951 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::obtainBuffer(android::AudioRecord::Buffer *, const struct timespec *, struct timespec *, size_t *) , 838 frameCount:512 
03-14 15:03:29.329  2239  2951 D Debug_dump_info:: buffer = [B@7be48d1, num = 2048
03-14 15:03:29.330  2239  2951 D AudioRecord: Debug_dump_info: ssize_t android::AudioRecord::read(void *, size_t, bool) , 881 
03-14 15:03:29.351   230  2278 D AudioHardwareTiny: Debug_dump_info: frames_wr:0,buf.frame_count:1024,frame_size:4====
03-14 15:03:29.351   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read line 2167 , startPos: 0 
03-14 15:03:29.352   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read: line:2128 
03-14 15:03:29.352  2239  2951 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::obtainBuffer(android::AudioRecord::Buffer *, const struct timespec *, struct timespec *, size_t *) , 838 frameCount:512 
03-14 15:03:29.352  2239  2951 D Debug_dump_info:: buffer = [B@7be48d1, num = 2048
03-14 15:03:29.352  2239  2951 D AudioRecord: Debug_dump_info: ssize_t android::AudioRecord::read(void *, size_t, bool) , 881 
03-14 15:03:29.352  2239  2951 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::obtainBuffer(android::AudioRecord::Buffer *, const struct timespec *, struct timespec *, size_t *) , 838 frameCount:512 
03-14 15:03:29.352  2239  2951 D Debug_dump_info:: buffer = [B@7be48d1, num = 2048

step 5: AudioRecord 结束过程

用户点击按钮停止录音,调用 JNI 接口是 android_media_AudioRecord_stop() 对应着 AudioRecord::stop() 方法,源码如下:

void AudioRecord::stop()
{
    AutoMutex lock(mLock);
    if (!mActive) {
        return;
    }

    mActive = false;
    mProxy->interrupt();
    mAudioRecord->stop();

    // Note: legacy handling - stop does not clear record marker and
    // periodic update position; we update those on start().
    ALOGD("Debug_dump_info: %s , %d ", __func__, __LINE__);
    sp<AudioRecordThread> t = mAudioRecordThread;
    if (t != 0) {
        t->pause();
    } else {
        setpriority(PRIO_PROCESS, 0, mPreviousPriority);
        set_sched_policy(0, mPreviousSchedulingGroup);
    }
}

此部分牵扯出 AudioPoxy 相关内容,我不在此展开,我们知道是通过 JNI 接口来停止录音的。姑且这样吧。调试日志如下:

03-14 15:03:43.938  2239  2951 D AudioRecord: Debug_dump_info: ssize_t android::AudioRecord::read(void *, size_t, bool) , 881 
03-14 15:03:43.942  2239  2239 D Debug_dump_info:: onClick StopRecord									//> 点击停止录音
03-14 15:03:43.958   230  2278 D AudioHardwareTiny: Debug_dump_info: frames_wr:0,buf.frame_count:1024,frame_size:4====
03-14 15:03:43.958   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read line 2167 , startPos: 0 
03-14 15:03:43.959   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read: line:2128 
03-14 15:03:43.959  2239  2951 D AudioRecord: Debug_dump_info: status_t android::AudioRecord::obtainBuffer(android::AudioRecord::Buffer *, const struct timespec *, struct timespec *, size_t *) , 838 frameCount:512 
03-14 15:03:43.960  2239  2951 D Debug_dump_info:: buffer = [B@7be48d1, num = 2048
03-14 15:03:43.960  2239  2951 D Debug_dump_info:: exit loop                              //> 退出录音
03-14 15:03:43.981   230  2278 D AudioHardwareTiny: Debug_dump_info: frames_wr:0,buf.frame_count:1024,frame_size:4====
03-14 15:03:43.981   230  2278 D AudioHardwareTiny: Debug_dump_info: in_read line 2167 , startPos: 0 
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info: in_set_parameters: kvpairs = routing=0
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters : 1994 source: -2 
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters : 2000 in->device:256 
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters , 2012 Device:256 
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info:  in_set_parameters , 2018 
03-14 15:03:43.986   230  2278 D AudioHardwareTiny: Debug_dump_info: in_set_parameters: exit: status(0)
03-14 15:03:43.986   230   230 D APM_AudioPolicyManager: Debug_dump_info: resetInputDevice() releaseAudioPatch returned 0
03-14 15:03:43.987  2239  2951 D AudioRecord: Debug_dump_info: void android::AudioRecord::stop() , 364      //> 停止录音
03-14 15:03:43.988  2239  2951 D AudioRecord: Debug_dump_info: virtual android::AudioRecord::AudioRecordThread::~AudioRecordThread() , 1263 
03-14 15:03:43.989   230   300 D APM_AudioPolicyManager: Debug_dump_info: void android::AudioPolicyManager::closeInput(audio_io_handle_t),4245 closeInput(30) //> 关闭输入流
03-14 15:03:44.091  2239  2951 D Debug_dump_info:: out wav format file:/voice-sub.wav

简单总结下:
1>. 用户 new AudioRecord() 调用是 audio_setup() 方法,初始化 input 设备;
2>. 开始录音、调用是 audio_start() 方法,打开声卡设备并读取音频流数据;
3>. 停止录音、调用是 audio_stop() 方法,关闭输入设备。

至此连续三篇内容,基本把通过 snd-aloop 虚拟声卡、实现 Android系统声音录制全过程给如实的说清楚了,我把录制的音频文件放到网盘上,
如有需要请自行下载。
链接:https://pan.baidu.com/s/1JpQbdJXGM2wB01I_6MsBUA
提取码:8bu6

如果有读者想验证这个内容的话,其中还涉及到 android 系统相关属性配置、编译文件 Android.mk 内容修改,后期我有时间会继续分享
《Android 8.1 系统裁剪、定制化实践 snd-aloop 内录音》内容。

附录 pcmToWav.java 源码


public class PcmToWavUtil {

    /**
     * 缓存的音频大小
     */
    private int mBufferSize;
    /**
     * 采样率
     */
    private int mSampleRate;
    /**
     * 声道数
     */
    private int mChannel;


    /**
     * @param sampleRate sample rate、采样率
     * @param channel channel、声道
     * @param encoding Audio data format、音频格式
     */
    PcmToWavUtil(int sampleRate, int channel, int encoding) {
        this.mSampleRate = sampleRate;
        this.mChannel = channel;
        this.mBufferSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, encoding);
    }


    /**
     * pcm文件转wav文件
     *
     * @param inFilename 源文件路径
     * @param outFilename 目标文件路径
     */
    public void pcmToWav(String inFilename, String outFilename) {
        FileInputStream in;
        FileOutputStream out;
        long totalAudioLen;
        long totalDataLen;
        long longSampleRate = mSampleRate;
        int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
        long byteRate = 16 * mSampleRate * channels / 8;
        byte[] data = new byte[mBufferSize];
        try {
            in = new FileInputStream(inFilename);
            out = new FileOutputStream(outFilename);
            totalAudioLen = in.getChannel().size();
            totalDataLen = totalAudioLen + 36;

            writeWaveFileHeader(out, totalAudioLen, totalDataLen,
                    longSampleRate, channels, byteRate);
            while (in.read(data) != -1) {
                out.write(data);
            }
            in.close();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    /**
     * 加入wav文件头
     */
    private void writeWaveFileHeader(FileOutputStream out, long totalAudioLen,
                                     long totalDataLen, long longSampleRate, int channels, long byteRate)
            throws IOException {
        byte[] header = new byte[44];
        // RIFF/WAVE header
        header[0] = 'R';
        header[1] = 'I';
        header[2] = 'F';
        header[3] = 'F';
        header[4] = (byte) (totalDataLen & 0xff);
        header[5] = (byte) ((totalDataLen >> 8) & 0xff);
        header[6] = (byte) ((totalDataLen >> 16) & 0xff);
        header[7] = (byte) ((totalDataLen >> 24) & 0xff);
        //WAVE
        header[8] = 'W';
        header[9] = 'A';
        header[10] = 'V';
        header[11] = 'E';
        // 'fmt ' chunk
        header[12] = 'f';
        header[13] = 'm';
        header[14] = 't';
        header[15] = ' ';
        // 4 bytes: size of 'fmt ' chunk
        header[16] = 16;
        header[17] = 0;
        header[18] = 0;
        header[19] = 0;
        // format = 1
        header[20] = 1;
        header[21] = 0;
        header[22] = (byte) channels;
        header[23] = 0;
        header[24] = (byte) (longSampleRate & 0xff);
        header[25] = (byte) ((longSampleRate >> 8) & 0xff);
        header[26] = (byte) ((longSampleRate >> 16) & 0xff);
        header[27] = (byte) ((longSampleRate >> 24) & 0xff);
        header[28] = (byte) (byteRate & 0xff);
        header[29] = (byte) ((byteRate >> 8) & 0xff);
        header[30] = (byte) ((byteRate >> 16) & 0xff);
        header[31] = (byte) ((byteRate >> 24) & 0xff);
        // block align
        header[32] = (byte) (2 * 16 / 8);
        header[33] = 0;
        // bits per sample
        header[34] = 16;
        header[35] = 0;
        //data
        header[36] = 'd';
        header[37] = 'a';
        header[38] = 't';
        header[39] = 'a';
        header[40] = (byte) (totalAudioLen & 0xff);
        header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
        header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
        header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
        out.write(header, 0, 44);
    }
}

你可能感兴趣的:(Android-系列,android,音视频,语音识别)