博主QQ:1356438802

QQ群:473383394——UVC&OpenCV47



在Android多媒体类,MediaMuxer和MediaCodec这是一个相对年轻,他们是JB 4.1和JB 4.3据介绍。

前者被用来产生一个混合的音频和视频的多媒体文件。的缺点是,现在可以只支持一个audio track而一个video track,而唯一支持mp4出口。然是新生事物,相信之后的版本号应该会有大的改进。MediaCodec用于将音视频进行压缩编码,它有个比較牛X的地方是能够对Surface内容进行编码,如KK 4.4中屏幕录像功能就是用它实现的。

注意它们和其他一些多媒体相关类的关系和差别:MediaExtractor用于音视频分路,和MediaMuxer正好是反过程。MediaFormat用于描写叙述多媒体数据的格式。MediaRecorder用于录像+压缩编码。生成编码好的文件如mp4, 3gpp,视频主要是用于录制Camera preview。MediaPlayer用于播放压缩编码后的音视频文件。

AudioRecord用于录制PCM数据。AudioTrack用于播放PCM数据。PCM即原始音频採样数据。能够用如vlc播放器播放。

当然了,通道採样率之类的要自己设。由于原始採样数据是没有文件头的,如:
vlc --demux=rawaud --rawaud-channels 2 --rawaud-samplerate 44100 audio.pcm

回到MediaMuxer和MediaCodec这两个类,它们的參考文档见http://developer.android.com/reference/android/media/MediaMuxer.html和http://developer.android.com/reference/android/media/MediaCodec.html。里边有使用的框架。这个组合能够实现非常多功能,比方音视频文件的编辑(结合MediaExtractor),用OpenGL绘制Surface并生成mp4文件,屏幕录像以及类似Camera app里的录像功能(尽管这个用MediaRecorder更合适)等。



这里以一个非常无聊的功能为例,就是在一个Surface上绘图编码生成视频。同一时候用MIC录音编码生成音频,然后将音视频混合生成mp4文件。

程序本身没什么用。可是演示样例了MediaMuxer和MediaCodec的基本使用方法。本程序主要是基于两个測试程序:一个是Grafika中的SoftInputSurfaceActivity和HWEncoderExperiments。它们一个是生成视频,一个生成音频,这里把它们结合一下,同一时候生成音频和视频。基本框架和流程例如以下:


首先是录音线程,主要參考HWEncoderExperiments。

通过AudioRecord类接收来自麦克风的採样数据,然后丢给Encoder准备编码:

AudioRecord audio_recorder;audio_recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,               SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, buffer_size);                        // ...audio_recorder.startRecording();while (is_recording) {    byte[] this_buffer = new byte[frame_buffer_size];    read_result = audio_recorder.read(this_buffer, 0, frame_buffer_size); // read audio raw data    // …    presentationTimeStamp = System.nanoTime() / 1000;    audioEncoder.offerAudioEncoder(this_buffer.clone(), presentationTimeStamp);  // feed to audio encoder}
这里也能够设置AudioRecord的回调(通过setRecordPositionUpdateListener())来触发音频数据的读取。offerAudioEncoder()里主要是把audio採样数据送入音频MediaCodec的InputBuffer进行编码:

ByteBuffer[] inputBuffers = mAudioEncoder.getInputBuffers();int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) {    ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];    inputBuffer.clear();    inputBuffer.put(this_buffer);    ...    mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, this_buffer.length, presentationTimeStamp, 0);}
以下,參考Grafika-SoftInputSurfaceActivity,并增加音频处理。

主循环大体分四部分:

try {    // Part 1    prepareEncoder(outputFile);    ...    // Part 2    for (int i = 0; i < NUM_FRAMES; i++) {        generateFrame(i);        drainVideoEncoder(false);        drainAudioEncoder(false);    }    // Part 3    ...    drainVideoEncoder(true);    drainAudioEncoder(true);}  catch (IOException ioe) {    throw new RuntimeException(ioe);} finally {    // Part 4    releaseEncoder();}
第1部分是准备工作。除了video的MediaCodec,这里还初始化了audio的MediaCodec:

MediaFormat audioFormat = new MediaFormat();audioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);audioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);...        mAudioEncoder = MediaCodec.createEncoderByType(AUDIO_MIME_TYPE);mAudioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mAudioEncoder.start();
第2部分进入主循环,app在Surface上直接画图,因为这个Surface是从MediaCodec中用createInputSurface()申请来的,所以画完后不用显式用queueInputBuffer()交给Encoder。

drainVideoEncoder()和drainAudioEncoder()分别将编码好的音视频从buffer中拿出来(通过dequeueOutputBuffer()),然后交由MediaMuxer进行混合(通过writeSampleData())。

注意音视频通过PTS(Presentation time stamp。决定了某一帧的音视频数据何时显示或播放)来同步,音频的time stamp需在AudioRecord从MIC採集到数据时获取并放到对应的bufferInfo中,视频因为是在Surface上画,因此直接用dequeueOutputBuffer()出来的bufferInfo中的即可,最后将编码好的数据送去MediaMuxer进行多路混合。



注意这里Muxer要等把audio track和video track都增加了再開始。

MediaCodec在一開始调用dequeueOutputBuffer()时会返回一次INFO_OUTPUT_FORMAT_CHANGED消息。

我们仅仅需在这里获取该MediaCodec的format,并注冊到MediaMuxer里。

接着推断当前audio track和video track是否都已就绪,假设是的话就启动Muxer。

总结来说,drainVideoEncoder()的主逻辑大致例如以下,drainAudioEncoder也是类似的。仅仅是把video的MediaCodec换成audio的MediaCodec就可以。


while(true) {    int encoderStatus = mVideoEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {        ...    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {        encoderOutputBuffers = mVideoEncoder.getOutputBuffers();    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {        MediaFormat newFormat = mAudioEncoder.getOutputFormat();        mAudioTrackIndex = mMuxer.addTrack(newFormat);        mNumTracksAdded++;        if (mNumTracksAdded == TOTAL_NUM_TRACKS) {            mMuxer.start();        }    } else if (encoderStatus < 0) {        ...    } else {        ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];        ...        if (mBufferInfo.size != 0) {            mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo);        }        mVideoEncoder.releaseOutputBuffer(encoderStatus, false);        if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {            break;                }    }}
第3部分是结束录制。发送EOS信息。这样在drainVideoEncoder()和drainAudioEncoder中就能够依据EOS退出内循环。

第4部分为清理工作。

把audio和video的MediaCodec,MediaCodec用的Surface及MediaMuxer对象释放。

最后几点注意:
1. 在AndroidManifest.xml里加上录音权限,否则创建AudioRecord对象时铁定失败:
 
2. 音视频通过PTS同步,两个的单位要一致。
3. MediaMuxer的使用要依照Constructor -> addTrack -> start -> writeSampleData -> stop 的顺序。假设既有音频又有视频,在stop前两个都要writeSampleData()过。

Code references:
Grafika: https://github.com/google/grafika
Bigflake: http://bigflake.com/mediacodec/
HWEncoderExperiments:https://github.com/OnlyInAmerica/HWEncoderExperiments/tree/audioonly/HWEncoderExperiments/src/main/java/net/openwatch/hwencoderexperiments
Android test:http://androidxref.com/4.4.2_r2/xref/cts/tests/tests/media/src/android/media/cts/ 
http://androidxref.com/4.4.2_r2/xref/pdk/apps/TestingCamera2/src/com/android/testingcamera2/CameraRecordingStream.java

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. python起点网月票榜字体反爬案例
  3. 《Android开发从零开始》——25.数据存储(4)
  4. Android系统配置数据库注释(settings.db)
  5. Android中不同应用间实现SharedPreferences数据共享
  6. android ndk编译x264开源(用于android的ffmpeg中进行软编码)
  7. android图表ichartjs
  8. Android内容提供者源码
  9. Android中文API(144) —— JsonWriter

随机推荐

  1. Android多点触摸实现
  2. Android USB Gadget复合设备驱动(打印机)
  3. Android的消息框处理方法
  4. Android(安卓)中自定义属性(attr.xml,Typ
  5. Android之BitTube
  6. Android(安卓)编译 ijkplayer
  7. Android中给Button加上selector——点击
  8. 【android】Eclipse ADT配置
  9. Android(安卓)Studio 混淆打包及常见第三
  10. 【Android】MapView和其它控件一起显示