120001 Android视频录制

本文提供几种可行的在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 encoder
	public MediaCodec VideoCodecEncoder, AudioCodecEncoder;
	
	//video frame property
	int 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); //关键帧间隔时间 单位s
		VideoCodecEncoder.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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值