主要用的安卓类有MediaCodec和MediaMuxer,MediaCodec负责视频数据编解码,MediaMuxer负责将编码后的数据封装成MP4文件,采集摄像头用的是camera,并且用surfaceview进行预览
1、初始化surfaceview与camera,预览摄像头的画面

  private void initSurfaceHolder() {surfaceHolder = surfaceView.getHolder();surfaceHolder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(SurfaceHolder holder) {initCamera();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {mCamera.startPreview();}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {destroyCamera();}});}

在surfaceview surfaceCreated中初始化摄像机配置。

  private void initCamera() {mCamera = Camera.open(1);mCamera.setPreviewCallback(this);mCamera.setDisplayOrientation(90);if (parameters == null) {parameters = mCamera.getParameters();}parameters.setPreviewFormat(ImageFormat.NV21);parameters.setPreviewSize(1280, 720);mCamera.setParameters(parameters);try {mCamera.setPreviewDisplay(surfaceHolder);} catch (IOException e) {e.printStackTrace();}}

注意由于摄像头默认是横着的,所以必须先调用setDisplayOrientation 翻转下,ImageFormat.NV21是摄像机默认的图像采样格式。
获取原始YUV数据:

 @Overridepublic void onPreviewFrame(byte[] data, Camera camera) {if (YUVQueue.size() > 10) {YUVQueue.poll();}YUVQueue.add(data);}

onPreviewFrame 回调中获取到的就是原始YUV数据,这里,我先将它放到队列中,以方便后面编码从中取出。

2、摄像头采集到的画面的帧数据拿到后,下一步当然是进行编码工作,编码用到的是MediaCodec,我们先初步了解下MediaCodec。
MediaCodec类可以获取底层媒体编码/解码库,是Android底层多媒体支持库的一部分(一般和MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface、AudioTrack搭配使用)。

简单来说,MediaCodec 工作时候有输入工作队列和输出工作队列,我们将摄像头原始画面数据放进空的buffer中,并且送给编码器,编码器对其进行编码,编码后的数据又通过output buffer队列输出来,我们取出编码后的buffer,并将其释放掉,再送给编码器。
下面开始代码初始化MediaCodec.

 //编码格式,AVC对应的是H264MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);//YUV 420 对应的是图片颜色采样格式mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);//比特率mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);//帧率mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);//I 帧间隔mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);try {mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);//创建生成MP4初始化对象mediaMuxer = new MediaMuxer(MP4_PATH, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);} catch (IOException e) {e.printStackTrace();}//进入配置状态mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);//进行生命周期执行状态mediaCodec.start();

比特率,帧率根据需要自己设置。
另外 摄像头采集到的NV21数据,即YYYYYYYY VUVU,编码器需要的是NV12,即
YYYYYYY UVUV

 private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {if (nv21 == null || nv12 == null) {return;}int framesize = width * height;int i, j;System.arraycopy(nv21, 0, nv12, 0, framesize);for (i = 0; i < framesize; i++) {nv12[i] = nv21[i];}for (j = 0; j < framesize / 2; j += 2) {nv12[framesize + j - 1] = nv21[j + framesize];}for (j = 0; j < framesize / 2; j += 2) {nv12[framesize + j] = nv21[j + framesize - 1];}}

好了,YUV数据也转换好了,下面我们就进行编码工作了,这里我先介绍同步方式,开启一个线程专门做编码任务。

    class EncoderThread extends Thread {@Overridepublic void run() {long pts = 0;super.run();while (!isMuxFinish) {if (mediaCodec == null) {break;}// 拿到有空闲的输入缓存区下标int inputBufferId = mediaCodec.dequeueInputBuffer(-1);if (inputBufferId >= 0) {pts = computePresentationTime();//有效的空的缓存区ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId);byte[] tempByte = getInputBuffer();if(isMuxFinish){break;}inputBuffer.put(tempByte);//将数据放到编码队列mediaCodec.queueInputBuffer(inputBufferId, 0, tempByte.length, pts, 0);}MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//得到成功编码后输出的out buffer Idint outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);if (outputBufferId >= 0) {ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);byte[] out = new byte[bufferInfo.size];outputBuffer.get(out);writeBytesToFile(out);outputBuffer.position(bufferInfo.offset);outputBuffer.limit(bufferInfo.offset + bufferInfo.size);// 将编码后的数据写入到MP4复用器mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);//释放output buffermediaCodec.releaseOutputBuffer(outputBufferId, false);} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {MediaFormat mediaFormat = mediaCodec.getOutputFormat();mTrackIndex = mediaMuxer.addTrack(mediaFormat);mediaMuxer.start();}}

注意事项:
1、dequeueInputBuffer 得到input buffer ,如果返回-1表示没有可用的buffer,参数timeoutUs ,如果传的是0,将会立即返回,传的是-1,将会一直等待。
2、mediaCodec.queueInputBuffer(inputBufferId, 0, tempByte.length, pts, 0);
将装满帧数据的buffer放进输入队列,该方法第四个参数表示时间戳,这个参数最好给下值,否则output后面不会输出,这个是我实践中发现的。
3、MP4合成器mediaMuxer.addTrack(mediaFormat)一定要在INFO_OUTPUT_FORMAT_CHANGED条件满足后再调用,否则不起作用。
4、编码后调用mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, info) 写入一帧数据或者一片数据
5、编码好一帧后记得releaseOutputBuffer

GitHub地址
https://github.com/zxd1991/AndroidMedia

安卓采集摄像头画面生成MP4文件相关推荐

  1. 解决ffmpeg生成mp4文件不能正常预览的问题

    框架的使用极大的提高了开发效率,让我们能够有更多的精力去关注系统的整体架构和业务需求,而不需要过多的关注一些具体的实现细节,但任何事情都有两面性,如果某个细节出了问题,由于对具体的实现不了解,那么会给 ...

  2. 利用FFmpeg转码生成MP4文件

    利用FFmpeg转码生成MP4文件 2017年06月24日 14:42:53 阅读数:2401 项目中,需要把一路音频流及一路视频流分别转码,生成指定格式(MP4)文件.在使用ffmpeg转码生成mp ...

  3. Android中如何提取和生成mp4文件

    1. MediaExtractor 该类主要用于音视频混合数据的分离,接口比较简单,首先要通过setDataSource(String path)函数设置数据源,数据源可以是本地文件地址,也可以使用H ...

  4. 从网页采集图片,生成PPT文件

    家里领导下了一个任务,把一个网页转成PPT.网页上有几十个图片,转成PPT,每个页面一个图片.说要是做的好,以后还有类似的网页要转.作为新时代的码农,总不能用CTRL+C 和 CTRL+V解决嘛. 先 ...

  5. 使用海康摄像头保存的mp4文件,无法web端预览的问题,现已解决,记录一下

    最近在做一个项目,需要用到海康的录像视频,在自己开发的web端能够正常播放,格式是MP4的,本来以为是个简单的需求,没想到遇到了好多坑,特此记录: 首先,由于是联动系统,项目用到的录像文件存到的是另一 ...

  6. android 屏幕适配dimens,关于android:安卓屏幕适配一键生成dimens文件

    工具类 import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.Pr ...

  7. 学习日记-AE使用Adobe Media Enconde生成mp4文件

    1.进入或者制作好ae文件(.aep),ctrl+m 进入渲染列表 2.点击'自定义',选择格式为'QuickTime'(这个格式速度会快很多,并且大小减少很多),点击确定,回到列表,点击'渲染' 3 ...

  8. 从零开始编译安卓系统源码(生成.img文件)以及安卓系统内核

    最近开始接触安卓设备,了解到安卓系统开机界面以及定制桌面需要修改系统源码,这种情况下就需要重新编译系统源码得到.img镜像文件,本篇文章记录从零开始编译安卓系统源码以及安卓系统内核的一种方式. 很感谢 ...

  9. Android 用MediaCodec ,MediaExtractor解码播放MP4文件

    上一篇讲了如何采集摄像头画面并且进行编码,再进行封装成MP4格式文件,如需了解可以看 安卓采集摄像头画面生成MP4文件 本篇博客,主要讲解如何对MP4文件进行解封装,再进行解H264码流,进行画面显示 ...

最新文章

  1. 【洛谷p1313】计算系数
  2. Codeforces 524F And Yet Another Bracket Sequence 哈希
  3. 贵州计算机专业强的专科学校,贵州排名靠前的五年制大专学校--贵州计算机学校...
  4. php fopen插入文本_PHP 文件创建/写入
  5. STM32 I2C通信(读写eeprom)
  6. Nginx on Docker 配置
  7. 笨办法学 Python · 续 练习 28:`sh`
  8. 日本新年传统习俗介绍(二)
  9. webp批量转换jpg_转换文件格式快人一步!右键菜单直接转换,支持图片、文档、视频等……...
  10. 让盘古分词支持最新的Lucene.Net 3.0.3
  11. F - Good Words
  12. 手把手教你搭建FastDFS集群(上)
  13. 回网友:不用PPT怎么做咨询顾问?
  14. 【Android 学习记录】:针对Android 7.0 抓不到HTTPS包的情况
  15. 天猫精灵开发技能【2】
  16. 2020年计算机设计大赛 人流量预测 (国赛三等奖)
  17. python小程序之天天向上
  18. Android Studio 搭建微信界面
  19. Python 一个好用到爆炸的IP端口扫描工具类
  20. 世人笑我太疯癫,我笑他人看不穿

热门文章

  1. 利用sqoop将oracle 11g中的表迁移至hive表
  2. 使用Visual Studio.Net,系统报告“automation服务器不能创建对象”错误。解决之道运行:regsvr32 scrrun.dll 就可以了。
  3. python右对齐乘法表_python如何打印99乘法表
  4. 李开复 —— 给中国学生的第四封信:大学四年应是这样度过
  5. 江淮汽车:受疫情和芯片短缺影响 Q1预亏3.07亿元
  6. 特斯拉CEO马斯克:可能明年3月左右在中国推出Model S Plaid
  7. 小鹏汽车9月总交付10412台 成为新造车势力中第一家月交付过万的企业
  8. 曾比海底捞还牛,如今关店1200家!肯德基的猪队友,快被中国人抛弃了?
  9. 华为高管预告手机鸿蒙OS下月上线,华为手机部:我们没说过
  10. 在家办公上课成强需求 钉钉峰值流量暴增百倍