120001 Android视频录制

来源:互联网 发布:阿里云服务器做vpn 编辑:程序博客网 时间:2024/06/03 22:02

本文提供几种可行的在Android端进行录像的方案,对于MP4文件的格式请自行在网上查阅。其中的有些方法经过了个人实践,贴出了核心代码,有一些由于不满足项目需要,没有实践,贴出一些参考资料。

1 常规录制

1.1 场景描述

       直接从摄像头和麦克风取数据,经过编码,保存为文件。

1.2 采用方法

       这种情况可以直接调用Android的MediaRecorder类。该类获得数据后,通过硬编码,然后保存成文件。操作流程固定。

1.3 核心代码

<span style="white-space:pre"></span>MediaRecorder mMediaRecorder;<span style="white-space:pre"></span>mMediaRecorder=new MediaRecorder();          //设置视频源          mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT);          //设置音频源          mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);          //设置文件输出格式          mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);          //设置视频编码方式          mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);          //设置音频编码方式          mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);          //设置视频高和宽,注意文档的说明:          //Must be called after setVideoSource().          //Call this after setOutFormat() but before prepare().          //设置录制的视频帧率,注意文档的说明:          //Must be called after setVideoSource().          //Call this after setOutFormat() but before prepare().          mMediaRecorder.setVideoFrameRate(15);          //设置预览画面          mMediaRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());          //设置输出路径          mMediaRecorder.setOutputFile          (Environment.getExternalStorageDirectory()+File.separator+System.currentTimeMillis()+".mp4");          mMediaRecorder.setVideoSize(640, 480);          //设置视频的最大持续时间          mMediaRecorder.setMaxDuration(10000);          //为MediaRecorder设置监听          mMediaRecorder.setOnInfoListener(new OnInfoListener() {              public void onInfo(MediaRecorder mr, int what, int extra) {                  if (what==MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {                      System.out.println("已经达到最长录制时间");                      if (mMediaRecorder!=null) {                          mMediaRecorder.stop();                          mMediaRecorder.release();                          mMediaRecorder=null;                      }                  }                                }          });


2 可修改数据方式进行录像

2.1 场景描述

       获取摄像头数据,进行处理,对处理过后视频数据进行编码,同时获得音频数据,进行处理,同样对处理后数据进行编码,将编码后的到数据保存为文件。

2.2 可选方法

(1)对于Android 4.3之后,可以通过MediaCodec和MediaMuxer配合进行录制。处理过后的数据,通过MediaCodec进行编码,然后通过MediaMuxer进行混合。

参考:http://blog.csdn.net/jinzhuojun/article/details/32163149

(2)利用开源库

       ffmpeg:http://blog.csdn.net/leixiaohua1020/article/details/15811977这篇博文整理的很好。

                      http://blog.csdn.net/shaoyizhe2006/article/details/8525738写MP4的例子。

(3)利用MediaCodec库和MP4V2进行录制, MediaCodec只在Android 4.1之后存在。MediaCodec调用硬编码,不占CPU,因此该方法可以保证效率。利用MediaCodec进行编码后,调用封装的MP4V2的写MP4类写成文件。

2.3 方案三细节

2.3.1 MediaCodec的调用

       该类主要对数据进行编码,具体操作流程如下:首先对编码器进行初始化,然后在编码器的输入数据队列中添加数据。编码得到的数据从编码器的输出队列中获取。

       (1)初始化编码器

<span style="white-space:pre"></span>//video and audio encoderpublic MediaCodec VideoCodecEncoder, AudioCodecEncoder;//video frame propertyint m_FrameWidth = 320;     //视频帧宽   <span style="white-space:pre"></span>int m_FrameHeight = 240;  //视频帧高<span style="white-space:pre"></span>int m_VideoFrameRate = 20;  //视频帧率 <span style="white-space:pre"></span>int m_VideoBitRate = 2000000;//视频比特率<span style="white-space:pre"></span>int m_AudioSamleRate = 22050;//音频采样频率<span style="white-space:pre"></span>int m_AudioChannelCout = 1;//音频通道数<span style="white-space:pre"></span>int m_AudioBitRate = 128000;//音频比特率 <span style="white-space:pre"></span>private int InitEncoder(int width, int height, int videobitrate,int videoframerate, int audiosampleRate, int audiochannelCount, int audiobitrate){VideoCodecEncoder = MediaCodec.createEncoderByType("video/avc"); //"video/avc"为MIME类型,可以查阅支持的类型MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, videobitrate);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, videoframerate);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); //关键帧间隔时间 单位sVideoCodecEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);VideoCodecEncoder.start();AudioCodecEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm");MediaFormat AudioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", audiosampleRate, audiochannelCount); 
<span style="white-space:pre"></span> //尝试过的手机只支持这种MIME类型AudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);//测试发现,KEY_BIT_RAT必须设置,否则编码器无法设置成功AudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, audiobitrate);//m.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mBuffer_Size);AudioCodecEncoder.configure(AudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);AudioCodecEncoder.start();return 0;}
(2)在输入队列中添加数据
<span style="white-space:pre"></span>//write videodata h264//输入视频帧数据格式为YUV420,即YYYYUVUVUVUV//摄像头支持的数据YV12数据排列方式为YYYYUUUUVVVV,需要转换public int WriteVideoData(byte[] YUV420Data){ByteBuffer[] inputBuffers = VideoCodecEncoder.getInputBuffers();        int inputBufferIndex = VideoCodecEncoder.dequeueInputBuffer(-1);        if (inputBufferIndex >= 0)         {            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];            inputBuffer.clear();            inputBuffer.put(YUV420Data); //放入数据            VideoCodecEncoder.queueInputBuffer(inputBufferIndex, 0, YUV420Data.length, 0, 0);        }        return 0;}//wirte audiodata aac//麦克风数据为PCM格式public int WriteAudioData(byte[] PCMData){ByteBuffer[] AudioinputBuffers = AudioCodecEncoder.getInputBuffers();        int AudioinputBufferIndex = AudioCodecEncoder.dequeueInputBuffer(-1);        if (AudioinputBufferIndex >= 0)         {            ByteBuffer inputBuffer = AudioinputBuffers[AudioinputBufferIndex];            inputBuffer.clear();            inputBuffer.put(PCMData);            AudioCodecEncoder.queueInputBuffer(AudioinputBufferIndex, 0, PCMData.length, 0, 0);        }return 0;}
(3)从输出队列取数据(以视频帧为例)

<span style="white-space:pre"></span>ByteBuffer[] outputBuffers = VideoCodecEncoder.getOutputBuffers();MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();        int outputBufferIndex = VideoCodecEncoder.dequeueOutputBuffer(bufferInfo,0);                  int pos = 0;        while (outputBufferIndex >= 0)         {            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];            byte[] outData = new byte[bufferInfo.size];            outputBuffer.get(outData);   //取编码后数据                        if(m_Video_SPSPPS != null)              {                        System.arraycopy(outData, 0, h264, pos, outData.length);             pos += outData.length;                        }            else //保存pps sps 只有开始时 第一个帧里有, 保存起来后面用 //音频第一帧也有信息,一般为2个字节            {             ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);                   if (spsPpsBuffer.getInt() == 0x00000001)                  {                   m_Video_SPSPPS = new byte[outData.length];                 System.arraycopy(outData, 0, m_Video_SPSPPS, 0, outData.length);                 }                  else                  {                          return -1;                 }              }                                    VideoCodecEncoder.releaseOutputBuffer(outputBufferIndex, false);            outputBufferIndex = VideoCodecEncoder.dequeueOutputBuffer(bufferInfo, 0);        }//音频不存在关键帧,因此需要添加头信息        byte input[] = new byte[h264.length];        if(h264[4] == 0x65) //key frame   编码器生成关键帧时只有 00 00 00 01 65 没有pps sps, 要加上        {        System.arraycopy(h264, 0,  input, 0, pos);        System.arraycopy(m_Video_SPSPPS, 0,  h264, 0, m_Video_SPSPPS.length);        System.arraycopy(input, 0,  h264, m_Video_SPSPPS.length, pos);        pos += m_Video_SPSPPS.length;        }

2.3.2 Mp4v2调用

       这是一个开源库:以下博文对该库的使用描述比较清楚。

http://blog.csdn.net/sweetloverft/article/details/29851309

包括编译方法,另外如果要在Android上使用,可以针对以下C++版本的调用程序(http://download.csdn.net/detail/nighterll/7592823)中的MP4Encoder类写个jni,然后和android库一起在cgwin下编译成so就可以使用。注意些Android.mk时按网上的写法,在需要编的.h和.cpp后加上MP4Encoder.h和MP4Encoder.cpp,以及自己写的jni的cpp和h文件。值得注意的是mp4v2在编译时cpp和h的编译时有顺序,尝试用通配符的方式(偷懒不想写那么多),出现错误,因此按各种帖子上的方式写,在最好加上自己的东西就可以了。值得一提的是C++版本的这个Demo非常好,已经测试过,很好用,结构清晰,操作简单。作者在CSDN上谦虚的称是封装的较好的,测试确实如此,程序直接可以编译运行,代码结构清晰,看起来不费力。可惜找了半天,找不到下载的地方了,我在上传一份,如果作者看到,请告知我一声,我把链接改到您的位置上。

具体调用过程为:先初始化文件,即建立一个MP4文件;然后写h264的track,需要给的参数就是sps和pps;接下来写AAC的track,给的信息也是一个头信息,似乎大部分都是2个字节;然后写h264的数据;接着写AAC数据;最后关闭文件。中间的步骤,只要对应的track在写数据之前写完,其他的没有什么顺序限制。

C++版本的调用实例:

MP4CreateFile("test.mp4", 5);MP4AddH264Track(buf, len, 640, 480);MP4AddAACTrack(buf, len);MP4WriteH264Data(buf, len, pts);MP4WriteAACData(buf, len, pts);MP4ReleaseFile();

写Data的两个方法可以循环调用,直到写完所有数据。


3 遗留问题

到此,一个可行的方案完成了,测试了录像。存在以下问题:

(1)视频出现马赛克,个人认为是编码器的两个队列处理的问题,由于硬编码速度快,写文件速度慢,而编码器的输出队列只缓存了4个包,前面还没有写完,后面编码输出就覆盖了原来包,导致丢包。不知道理解对不对,希望有高手可以解释。

(2)同步问题,现在给pts是在写数据的时候分别给的PTS,但是同时采集的视频帧处理时间远大于音频的处理时间,如果用采集时间作为PTS,编码器输出的是一个个的包,没有时间信息,不知道怎么做同步。从实际效果来看,现在用写的时间来记录,文件播放似乎也是同步的,但是这始终是个隐患。

遗留的这两个问题希望做相关研究的朋友一起讨论解决方案。




0 0
原创粉丝点击