spydroid-ipcamera源码分析(三):AudioStream和相关子类的音频流操作

AudioStream类

AudioStream类是音频流的基类,重写了MediaStream的encodeWithMediaRecorder方法,实现了MediaRecorder录制音频的操作。同时作为抽象类,它并没有重写encodeWithMediaCodec方法,而是留给子类去具体实现。

    @Override
    protected void encodeWithMediaRecorder() throws IOException {
        
        // We need a local socket to forward data output by the camera to the packetizer
        createSockets();

        Log.v(TAG,"Requested audio with "+mQuality.bitRate/1000+"kbps"+" at "+mQuality.samplingRate/1000+"kHz");
        
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setAudioSource(mAudioSource);
        mMediaRecorder.setOutputFormat(mOutputFormat);
        mMediaRecorder.setAudioEncoder(mAudioEncoder);
        mMediaRecorder.setAudioChannels(1);
        mMediaRecorder.setAudioSamplingRate(mQuality.samplingRate);
        mMediaRecorder.setAudioEncodingBitRate(mQuality.bitRate);
        
        // We write the ouput of the camera in a local socket instead of a file !           
        // This one little trick makes streaming feasible quiet simply: data from the camera
        // can then be manipulated at the other end of the socket
        mMediaRecorder.setOutputFile(mSender.getFileDescriptor());

        mMediaRecorder.prepare();
        mMediaRecorder.start();

        try {
            // mReceiver.getInputStream contains the data from the camera
            // the mPacketizer encapsulates this stream in an RTP stream and send it over the network
            mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
            mPacketizer.setInputStream(mReceiver.getInputStream());
            mPacketizer.start();
            mStreaming = true;
        } catch (IOException e) {
            stop();
            throw new IOException("Something happened with the local sockets :/ Start failed !");
        }
        
    }

重写encodeWithMediaRecorder方法,主要是MediaRecorder录制音频的操作。首先启动本地Socket,再对MediaRecorder对象设置音频来源、音频编码、音频质量等参数,然后开始录制。这里值得一提的是,MediaRecorder输出的音频数据是写入Socket中而不是文件中,这样就可以在Socket的另一端进行操作。最后一步是使用Packetizer对象将数据打包并发送到网络传输。

AACStream类

AACStream类是一个关于AAC音频格式的AudioStream的子类,内部封装了对AAC音频格式的配置和编码操作。

    private static boolean AACStreamingSupported() {
        if (Build.VERSION.SDK_INT<14) return false;
        try {
            MediaRecorder.OutputFormat.class.getField("AAC_ADTS");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

构造函数中会判断是否支持AAC编码格式。

        // Checks if the user has supplied an exotic sampling rate
        int i=0;
        for (;i12) mQuality.samplingRate = 16000;

configure()方法中的部分代码。在start()开始时需要先检查一下采样率配置信息,不符合规范则强行设置默认采样率。

    @Override
    protected void encodeWithMediaRecorder() throws IOException {
        testADTS();
        ((AACADTSPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
        super.encodeWithMediaRecorder();
    }

重写encodeWithMediaRecorder方法,testADTS()方法是先从麦克风记录AAC ADTS的简短样本,以了解该设备支持的真实的采样率,便于设置配置信息和防止报错。篇幅原因,这里就不贴出testADTS()的代码了。

下面我们开始分析重写encodeWithMediaCodec()方法里面的内容,我把逐句分析写在注释里面。

        //计算出缓冲区的大小
        final int bufferSize = AudioRecord.getMinBufferSize(mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)*2;
        //设置打包器的采样率
        ((AACLATMPacketizer)mPacketizer).setSamplingRate(mQuality.samplingRate);
        //实例化AudioRecord,参数依次为:声音来源、采样率、声道数、编码方式、缓冲区
        mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, mQuality.samplingRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
        //实例化MediaCodec编码器
        mMediaCodec = MediaCodec.createEncoderByType("audio/mp4a-latm");
        //配置信息依次为:格式、位速率、频道数、采样率、AAC文件、最大输入缓冲区
        MediaFormat format = new MediaFormat();
        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
        format.setInteger(MediaFormat.KEY_BIT_RATE, mQuality.bitRate);
        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mQuality.samplingRate);
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize);
        mMediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        //开始录制音频
        mAudioRecord.startRecording();
        mMediaCodec.start();
        
        //设置编码流,在后面的文章会详细讲到
        final MediaCodecInputStream inputStream = new MediaCodecInputStream(mMediaCodec);
        final ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

        //开启一个线程,读取和处理
        mThread = new Thread(new Runnable() {
            @Override
            public void run() {
                int len = 0, bufferIndex = 0;
                try {
                    //无限循环读取
                    while (!Thread.interrupted()) {
                        //从输入流队列中取数据进行编码操作(出队列)。
                        bufferIndex = mMediaCodec.dequeueInputBuffer(10000);
                        if (bufferIndex>=0) {
                            inputBuffers[bufferIndex].clear();
                            //从mAudioRecord读取数据到inputBuffers[bufferIndex]中
                            len = mAudioRecord.read(inputBuffers[bufferIndex], bufferSize);
                            if (len ==  AudioRecord.ERROR_INVALID_OPERATION || len == AudioRecord.ERROR_BAD_VALUE) {
                                Log.e(TAG,"An error occured with the AudioRecord API !");
                            } else {
                                //Log.v(TAG,"Pushing raw audio to the decoder: len="+len+" bs: "+inputBuffers[bufferIndex].capacity());
                                //输入流入队列(往编码器中添加数据做编码处理)
                                mMediaCodec.queueInputBuffer(bufferIndex, 0, len, System.nanoTime()/1000, 0);
                            }
                        }
                    }
                } catch (RuntimeException e) {
                    e.printStackTrace();
                }
            }
        });

        mThread.start();
        //把编码完成的数据流封装打包并进行网络传输
        // The packetizer encapsulates this stream in an RTP stream and send it over the network
        mPacketizer.setDestination(mDestination, mRtpPort, mRtcpPort);
        mPacketizer.setInputStream(inputStream);
        mPacketizer.start();

        mStreaming = true;

以上可以看到,重写encodeWithMediaCodec()方法主要流程就是:实例化和配置AudioRecord用来录取音频数据,实例化和配置MediaCodec用于对数据编码,开启一个线程循环读取AudioRecord录取的音频数据流,并将原始音频数据流添加到MediaCodec编码器中进行编码,然后将编码完成的数据流通过Packetizer打包器打包并发送出去。

AMRNBStream类

AMRNBStream类是一个关于ANR音频格式的AudioStream的子类。

@Override
    protected void encodeWithMediaCodec() throws IOException {
        super.encodeWithMediaRecorder();
    }

AMRNBStream可操作的动作不多,这里重写encodeWithMediaCodec()方法直接指向了super.encodeWithMediaRecorder()。关于AAC和AMR的比较可参考:AAC和AMR音频编码标准介绍

至此我们完整的了解了音频数据流的配置、采集、编码的整个过程,下一篇我们将分析VideoStream类和它的子类,详细了解视频流的配置、采集和编码的流程。

你可能感兴趣的:(spydroid-ipcamera源码分析(三):AudioStream和相关子类的音频流操作)