camera2对摄像头编码h264

MediaCodec编码摄像头数据

前置:保存的一些成员变量

// 摄像头开启的 handler
private Handler cameraHandler;
// Camera session 会话 handler
private Handler sessionHandler;
//这里是个Context都行
private AppCompatActivity mActivity;
// 这个摄像头所有需要显示的 Surface,以TextureView创建的Surface在一开始就已经加进去了,如果用SurfaceView的话,注意管理好SurfaceView因为onstop,onStart创建和销毁的Surface
private List<Surface> mViewSurfaces;
private CameraManager cameraManager;private String mCameraId;
private CameraDevice mDevice;
//Camera 配置信息
private CameraCharacteristics mCharacteristics;
private CameraCaptureSession mSession;// 编码输入的Surface,由MediaCodec创建
private Surface mStreamSurface;
// 摄像头选择的输出大小
private Size mSize;//编码器
private MediaCodec mMediaCodec;

一、选择要打开的摄像头,并保存对应配置参数。

  1. 获取打开的摄像头 ID,以及获取对应参数配置.
    try {
    String[] cameraIdList = cameraManager.getCameraIdList();
    // 遍历摄像头,获取第一个可用的摄像头,多个摄像头暂不播放
    for (String id : cameraIdList) {
    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
    // 获取该摄像头的说明数据
    int[] ints = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
    if (ints != null) {
    Arrays.stream(ints).filter(value -> value == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE).close();
    if (ints.length == 0) {
    Log.d(TAG, “the camera has not basic supports.”);
    continue;
    }
    mCameraId = id;
    mCharacteristics = cameraCharacteristics;
    // 由于目前无具体需求,使用第一个可连接的摄像头进行显示,选完就返回,有需要的获取配置参数自行判定
    break;
    }
    }
    } catch (CameraAccessException e) {
    throw new RuntimeException(e);
    }

    说明:CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE 只是为了确定这个 Camera可用而已。

  2. 获取摄像头的所有的输出尺寸,选一个合适的
    //获取配置参数
    StreamConfigurationMap configurationMap = mCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    //拿到对应 ImageFormat.YUV_420_888 所有可输出的尺寸,一般比较多,1280x720,1920x1080等等一大堆,传参有点重要下面说明会说.
    Stream stream = Arrays.stream(configurationMap.getOutputSizes(ImageFormat.YUV_420_888));
    //这里我选了宽高相乘最大的
    Optional max = stream
    .max(Comparator.comparingInt(item -> item.getWidth() * item.getHeight()));
    stream.close();
    //把输出尺寸保存起来,后面创建编码器要用,其实就是视频的分辨率
    max.ifPresent(value -> mSize = value);

3. 比较重要的参数configurationMap.getOutputSizes(ImageFormat.YUV_420_888)传参为什么是ImageFormat.YUV_420_888说明 比较重要的是,这个值不是随便设置的,是通过获取设备参数之后选的,只要是设备参数里有的都能用,但是没有的值,用了可是会出错的。

获取Camera图像格式的方法:
int[] outputFormats = configurationMap.getOutputFormats();

遍历打印一下,选一个就行,但是尽量按照参数说明搭配需求去选。

二、创建编码器

  1. 通过步骤一拿到的输出尺寸,配置编码格式.(只设置了必要的几个参数,其他的自选加入)

     //创建编码格式MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", mSize.getWidth(), mSize.getHeight());//设置码率mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, (int) (mSize.getHeight() * mSize.getWidth() * 0.2));//设置帧率60mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);//设置颜色格式,因为这里用的自动编码,就不用yuv420自己手动编了,直接用surfacemediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);//设置关键帧产生速度 1,单位是(帧/秒)mediaFormat.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1f);//摄像头编码角度会出现90度的偏差,设置正向旋转90显示正常效果,你要喜欢歪头90看其实也行mediaFormat.setInteger(MediaFormat.KEY_ROTATION, 90);Log.d(TAG, "MediaCodec format: " + mediaFormat);
    

说明:
(1) 编码格式创建中,video/avc 表示编码为 h264 编码,编码其实就是为了降低视频大小.

(2) 码率设置中,后面的乘以0.2只是为了降低码率,不然太清晰了。。。正常的大多数是乘以5,这样看着清晰多,当然大小也会增加.

(3) 帧率设置为24以上就行,不然就是PPT。

(4) 关键帧产生速度SDK25之后可以设为float类型,关键帧就是 i 帧,设置之后只是说尽量按这个速度产生,肯定不会一模一样的,0或者复数就是希望没帧都是关键帧

  1. 创建编码器
    try {
    //按照“video/avc”创建编码器,硬编码是基于硬件的,厂商不同会有不一样的硬编码实现,编码是Encode,别用成Decode解码器
    mMediaCodec = MediaCodec.createEncoderByType(“video/avc”);
    } catch (IOException e) {
    e.printStackTrace();
    }
    //配置编码器,MediaCodec状态机就不说了,这里只提使用。参数4也是说明是编码器,和创建时不一致也会报错
    mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
    //创建输入Surface,和MediaFormat设置的KEY_COLOR_FORMAT参数是有联系的,format那里设错了就不能用自动挡的surface,且createInputSurface()方法必须在configure()之后,start()之前调用
    mStreamSurface = mMediaCodec.createInputSurface();
    //开启编码
    mMediaCodec.start();

说明:四个点
(1) MediaCodec.createEncoderByType(“video/avc”),创建编码器,MediaCodec.createDecoderByType(“video/avc”)是创建解码器的。byName那个的话想用得先获取硬件已实现的编码器的名字才能调,虽然不麻烦,但是懒得敲,而且因为硬件厂商实现不同,兼容性还得考虑。

(2) configure()很容易就报错,参数4编解码值是不一样的,以及MediaFormat的设置,如果和摄像头、手机不支持也会报错,所以前面写的那些代码就是为了拿的,不然随便写一个就行了。

(3) MediaCodec.createInputSuface()方法必须在 configure()配置编码之后,start()开始编码之前调用,点进实现也有说明,出现其实也挺好解决,你也可以用mMediaCodec.setInputSurface(创建的常显Surface)去用自己的Surface,没有常显Surface的话可以静态方法MediaCodec.createPersistentInputSurface()创建一个常显Surface.

(4) 在 start()开启编码之后,MediaFormat参数格式其实是可以改的,只是需要用Bundle,通过key-value的方式设值,然后mMediaCodec.setParameters(bundle)去动态设置。

三、操作摄像头

  1. 打开摄像头预览

摄像头 ID之前已经拿过了,所以直接open就行了。
cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
Log.d(TAG, "onOpened, camera: " + camera);
mDevice = camera;

            // Camera 打开之后,把创建的解码器 Surface 加入显示队列中addSurface(mStreamSurface);//创建请求CaptureRequest request = createCaptureRequest(mDevice, mViewSurfaces);//创建输出配置项List<OutputConfiguration> outputConfigurations = createOutputConfig(mViewSurfaces);try {SessionConfiguration config = new SessionConfiguration(SessionConfiguration.SESSION_REGULAR,outputConfigurations,new ThreadPoolExecutor(3, 5, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<>(25)),new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mSession = session;try {//到这个回调,摄像头就可以正常预览了,编码器也会输出编码数据到输出队列了mSession.setRepeatingRequest(request, null, sessionHandler);} catch (CameraAccessException e) {throw new RuntimeException(e);}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Log.e(TAG, "onConfigureFailed, camera: " + session);if (mSession != null) mSession.close();if (mDevice != null) mDevice.close();mDevice = null;}});mDevice.createCaptureSession(config);} catch (CameraAccessException e) {error();}}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Log.e(TAG, "onDisconnected, camera: " + camera);}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {Log.e(TAG, "onError, camera: " + camera + " ,error : " + error);}}, cameraHandler);
  1. 创建CaptureRequest请求的函数(因为涉及到多个Surface,就单独创建了)

    private CaptureRequest createCaptureRequest(CameraDevice cameraDevice, List surfaces) {
    CaptureRequest.Builder captureRequest;
    try {
    //简单预览模式创建,有要求可以根据api去选video这些
    captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    //聚焦模式,不设置也可以
    captureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
    } catch (CameraAccessException e) {
    throw new RuntimeException(e);
    }
    if (surfaces != null) {
    //把每一个需要输出摄像头数据的Surface都加入
    for (Surface surface : surfaces) {
    if (surface == null) {
    continue;
    }
    captureRequest.addTarget(surface);
    }
    }
    return captureRequest.build();
    }

  2. 创建List的函数,Surface比较多,用的比较新的api去创建的session,老的api已经被标注遗弃了,所以单独列个方法。

    private List createOutputConfig(List surfaces) {
    List configs = new ArrayList<>(surfaces.size());
    for (Surface surface : surfaces) {
    if (surface == null) {
    continue;
    }
    //SDK32,config.enableSurfaceSharing()开启分享后addSurface()添加新的输出面会有问题,所以就没以一个Config去添加所有Surface
    OutputConfiguration config = new OutputConfiguration(surface);
    //就只创建,啥也不做,有要求可以自己加
    configs.add(config);
    }
    return configs;
    }

四、获取编码器的编码数据

  1. 这部分网上挺多的,也不难.

    //这个是保存录像数据的
    FileOutputStream outputStream;
    //每个关键帧的数据前面都需要添加spsPps的数据,不然无法解码播放
    byte[] spsPps = null;

    boolean codecing = true;
    //编码输出信息的对象,赋值由MediaCodec做
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

         //当前可用的数据位置
    

    int outputBufferIndex;
    byte[] h264 = new byte[mCameraAdapter.getSize().getWidth() * mCameraAdapter.getSize().getHeight()];
    //死循环获取,也可以对MediaCodec设置callback获取
    while (codecing) {
    //获取未来一段时间可用的输出缓冲区,如果存在可用,返回值大于等于0,bufferInfo也会被赋值,最大等待时间是100000微秒,其实就是100毫秒
    outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100000);
    Log.i(TAG, “dequeue output buffer outputBufferIndex=” + outputBufferIndex);

     //如果当前输出缓冲区没有可用的,返回负值,不同值含义不一样,有需要做判定即可if (outputBufferIndex < 0) {                continue;}ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);Log.i(TAG, "Streaming   bufferInfo,flags: " + bufferInfo.flags+ ", size: " + bufferInfo.size+ ", presentationTimeUs: " + bufferInfo.presentationTimeUs+ ", offset: " + bufferInfo.offset);//调整数据位置,从offset开始。这样我们一会儿读取就不用传offset偏差值了。 outputBuffer.position(bufferInfo.offset);//改完位置,那肯定要改极限位置吧,不然你数据不就少了数据末尾长度为offset的这一小部分?这两步不做也可以,get的时候传offset也一样outputBuffer.limit(bufferInfo.offset + bufferInfo.size);//现在的spsPps还是空的,这是不能做写入文件,保存之类操作的if (spsPps == null) {//创建写入的文件,保存录像try {File file = new File(mContext.getDataDir().getAbsolutePath() + "camera.h264");boolean newFile = file.createNewFile();outputStream = new FileOutputStream(file, true);} catch (Exception ignored) {}// sps pps获取并保存,还有其他方式可以获取,不过不一定有效,最好检验一下ByteBuffer sps = mMediaCodec.getOutputFormat().getByteBuffer("csd-0");ByteBuffer pps = mMediaCodec.getOutputFormat().getByteBuffer("csd-1");byte[] spsBites = new byte[sps.remaining()];byte[] ppsBites = new byte[pps.remaining()];sps.get(spsBites);pps.get(ppsBites);spsPps = new byte[spsBites.length + ppsBites.length];System.arraycopy(spsBites, 0, spsPps, 0, spsBites.length);System.arraycopy(ppsBites, 0, spsPps, spsBites.length, ppsBites.length);} else {//现在已经有spsPps数据了,可以开始了try {if (bufferInfo.size > h264.length) {h264 = new byte[bufferInfo.size];}h264 = new byte[bufferInfo.size];//获取编码数据outputBuffer.get(h264, 0, bufferInfo.size);//数据校验一下,如果是关键帧需要在头部加入spsPps,校验完的trans,就是编码好的h264编码数据了byte[] trans = trans(h264, 0, bufferInfo.size, bufferInfo.flags);//保存录像数据到文件中.暂定2G大小if (outputStream.getChannel().size() < (2L << 30)) {outputStream.write(trans);}//结束标志到达if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {pusher.stop();isStreaming = false;}} catch (Exception e) {e.printStackTrace();} finally {//释放这个缓冲位置的数据,不释放就一直在,MediaCodec数据满了可不行mediaCodec.releaseOutputBuffer(outputBufferIndex, false);}}
    

    }

说明:
(1) 获取编码的 index,大于等于零才是有效的,不然不能用。

(2) 保存h264编码数据之前,需要先获取spsPps,不然关键帧之前没有spsPps,解码播放端也不加的话就没法看了。

(3) 获取数据注意offset,真正有效的数据是 bytes[offset] 到 bytes[offset+size]这个位置,至于你是get()的时候传offset拿,还是possition()limit()后传0拿,就看你心情了。

(4) 这个index的输出缓冲区用完之后,记得释放
(5) 这个录像保存只是做个样子,如果真是录像,建议用 MediaMuxer 来做,直接传判定 index 后 传 buffinfo 就行,简单方便。

  1. 这是校验是不是关键帧的,很简单明了

    private byte[] trans(byte[] h264, int offset, int length, int flags) {
    //关键帧判定
    if ((flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
    // 每个关键帧前面需要添加spsPps
    byte[] data = new byte[spsPps.length + length];
    System.arraycopy(spsPps, 0, data, 0, spsPps.length);
    System.arraycopy(h264, offset, data, spsPps.length, length);
    length = data.length;

         return data;} else {return h264;}
    

    }

五、播放保存的 h264 文件.

连上设备,我这边设置的文件位置是 Context.getDataDir().getAbsolutePath()/camera.h264,真实目录是 /data/data/包名/camera.h264,保存出来用vlc,或者ffmpeg命令ffplay播放就行了,亲测:windows自带的播放器无法解码(笑)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/207567.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入理解 Python 中的 eval 函数

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com eval 是 Python 中一个强大而灵活的函数&#xff0c;它允许将字符串作为代码执行。然而&#xff0c;由于其潜在的安全风险&#xff0c;使用时需要谨慎。本文将深入探讨 eval 函数的各个方面&#xff0c;包括基本…

delphi/python 实现小红书xhs用户作品列表和图片/视频无水印解析

技术学习&#xff0c;请勿用与非法用途&#xff01;&#xff01;&#xff01; 成品图用户作品列表接口 /api/sns/web/v1/user_posted?num30&cursor&user_id642bf0850000000011022c4e&image_scenes http Get方式&#xff0c;请求头需要带上x-s x-t签名验证笔记明细…

直流负载箱的技术发展趋势和创新有哪些?

直流负载箱广泛应用于电子、通信、航空航天等领域&#xff0c;随着科技的不断发展&#xff0c;直流负载箱也在不断创新和改进&#xff0c;直流负载箱在负载电流和电压的测量方面要求高精度和高稳定性。未来的发展趋势是提高负载箱的测量精度和稳定性&#xff0c;以满足更高要求…

记录一些好的文章

高效编写可维护代码&#xff1a; 如何高效编写可维护代码&#xff1f; | 菜鸟教程 (runoob.com)

计算平均分并输出低于平均分的学生成绩

从键盘上输入若干&#xff08;<20&#xff09;个学生的成绩&#xff0c;统计计算出平均成绩&#xff0c;并输出低于平均分的学生成绩&#xff0c;用输入负数结束输入。 输入格式: 在一行中输入若干&#xff08;<20&#xff09;个学生的实型成绩&#xff0c;用输入负数结…

uniapp 使用 $emit和$on——$on中无法为data中的变量赋值

问题在于this的指向&#xff0c; 解决办法是使用变量保存$on&#xff0c;其次再为data中的值赋值 以下是具体代码&#xff1a; 1、html代码&#xff1a; <view class"form_picker" click"selePositionFun()"><view class""><inp…

Git

第1章 Git 概述 Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目。 Git 易于学习&#xff0c;占地面积小&#xff0c;性能极快。 它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性。其性能优于 Subversi…

系统设计之数据库

为您的项目选择正确的数据库是一项复杂的任务。许多数据库选项都适合不同的用例&#xff0c;很快就会导致决策疲劳。 我们希望这份备忘单提供高级指导&#xff0c;以找到符合您项目需求的正确服务并避免潜在的陷阱。 注意&#xff1a;Google 关于其数据库用例的文档有限。尽管…

软件测试卷王的自述,我难道真的很卷?

前言 前段时间去面试了一个公司&#xff0c;成功拿到了offer&#xff0c;薪资也从12k涨到了18k&#xff0c;对于工作都还没两年的我来说&#xff0c;还是比较满意的&#xff0c;毕竟一些工作3、4年的可能还没我高。 我可能就是大家说的卷王&#xff0c;感觉自己年轻&#xff…

北邮22级信通院数电:Verilog-FPGA(12)第十二周实验(2)彩虹呼吸灯(bug已解决 更新至3.0)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 一.代码部分 1.1一些更新和讲解 1.2改正后的…

解密HubSpot CMS Hub:构建引人入胜的企业网站!

在数字化时代&#xff0c;网站是企业与客户互动的重要窗口。为了在竞争激烈的市场中脱颖而出&#xff0c;企业需要一个现代化、用户友好且高度可定制的网站。而HubSpot CMS Hub作为一款领先的内容管理系统&#xff0c;为企业提供了独特的优势&#xff0c;让网站建设变得更加轻松…

Private Set Intersection from Pseudorandom CorrelationGenerators 最快PSI!导览解读

目录 一、概述 二、相关介绍 三、性能对比 四、技术细节 1.KKRT 2.Pseudorandom Correlation Generators 3.A New sVOLE-Based BaRK-OPRF 4.BaRK-OPRF 五、总结 参考文献 一、概述 这篇文章的主要脉络和核心思想是探讨如何利用伪随机相关生成器&#xff08;PCG&#…

【AI】以大厂PaaS为例,看人工智能技术方案服务能力的方向(2/2)

目录 三、解决方案 3.1 人脸身份验证 3.2 图像审核&#xff08;暴恐、色情等&#xff09; 3.3 人脸会场签到 3.4 机器人视觉 3.5 视频审核 3.6 电商图文详情生成 3.7 智能客服 接上回&#xff1a; 【AI】以大厂PaaS为例&#xff0c;看人工智能技术方案服务能力的方向&…

Mybatis实用教程之XML实现动态sql

系列文章目录 1、mybatis简介及数据库连接池 2、mybatis中selectOne的使用 3、mybatis简单使用 4、mybatis中resultMap结果集的使用 Mybatis实用教程之XML实现动态sql 系列文章目录前言1. 动态条件查询2. 动态更新语句3. 动态插入语句4、其他标签的使用 前言 当编写 MyBatis 中…

力扣labuladong——一刷day67

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣582.杀掉进程二、力扣536.从字符串生成二叉树 前言 二叉树的递归分为「遍历」和「分解问题」两种思维模式&#xff0c;这道题需要用到「遍历」的思维模…

麒麟系统进入救援模式或者是crtl D界面排查方法

如出现以下图片的情况可能需要修复磁盘&#xff1a; V10GFB-desktop&#xff1a; 开机后发现一致卡在此界面&#xff1a; 按esc键后有以下报错信息说明在/etc/fstab里面编写的外挂磁盘的命令有问题 解决方法如下&#xff1a;进入单用户模式对/etc/fstab进行修改&#xff1a; …

springboot-mongodb-连接配置

文章目录 配置Maven依赖URL格式单节点配置示例副本集&#xff08;含连接池配置&#xff09; 配置Maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependenc…

智能优化算法应用:基于侏儒猫鼬算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于侏儒猫鼬算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于侏儒猫鼬算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.侏儒猫鼬算法4.实验参数设定5.算法结果6.参考…

facebook广告运营技巧

在Facebook上进行广告运营需要一定的技巧和策略。以下是一些建议&#xff1a; 明确目标&#xff1a;在创建广告之前&#xff0c;你需要明确你的目标。这可能包括增加品牌知名度、提高网站流量、增加销售等等。明确目标后&#xff0c;你可以将广告与这些目标相结合&#xff0c;…

【五分钟】熟悉python列表和元组的异同点【看这篇够用!建议收藏】

引言 Python&#xff0c;是一种广泛应用于数据科学、机器学习等领域的高级编程语言&#xff0c;支持多种丰富多样的数据类型&#xff0c;其中包括列表和元组。尽管这两种数据结构都可用于存储多个值&#xff0c;但它们在功能和特性上存在着明显的差异。在接下来的博客中&#…