美文网首页
spydroid-ipcamera源码分析(三):AudioSt

spydroid-ipcamera源码分析(三):AudioSt

作者: 管弦_ | 来源:发表于2017-05-31 17:12 被阅读0次

    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 (;i<AUDIO_SAMPLING_RATES.length;i++) {
                if (AUDIO_SAMPLING_RATES[i] == mQuality.samplingRate) {
                    mSamplingRateIndex = i;
                    break;
                }
            }
            // If he did, we force a reasonable one: 16 kHz
            if (i>12) 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源码分析(三):AudioSt

          本文链接:https://www.haomeiwen.com/subject/oasjfxtx.html