范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Android音视频采集那些事

  音视频采集
  在整个音视频处理的过程中,位于发送端的音视频采集工作无疑是整个音视频链路的开始。在 Android 或者 IOS 上都有相关的硬件设备——Camera 和麦克风作为输入源。本章我们来分析如何在 Android 上通过 Camera 以及录音设备采集数据。 Camera
  在 Android 上的图片/视频采集设备无疑就是 Camera 了,在 Android SDK API21 之前的版本只能使用 Camera1 ,在 API 21 之后 Camera1 已经被标记为 Deprecated ,Google 推荐使用 Camera2,下面我们来分别看一下。 Camera1
  我们先来看一下 Camera1 体系的部分类图。
  Camera 类是 Camera1 体系的核心类,该类还有好多内部类,如上图: Camera.CameraInfo 类表达 Camera 的前后(facing)和旋转(orientation)等 Camera 相关的信息。
  Camera.Parameters 类是 Camera 相关的参数设置比如设置预览 Size 以及设置旋转角度等。
  Camera 类拥有打开 Camera、设置参数、设置预览等 API,下面我们来看使用 Camera API 打开系统照相机的流程。
  1.在开启 Camera 之前先释放 Camera,这一步的目的是重置 Camera 的状态重置 Camera 的 previewCallback 为 null 调用 Camera 的 release 释放
  把 Camera 对象设置为 null /** *释放Camera */   private fun releaseCamera() {         //重置previewCallback为空       cameraInstance!!.setPreviewCallback(null)       cameraInstance!!.release()       cameraInstance = null   }
  2.获取 Camera 的 Id /** *获取Camera Id */  private fun getCurrentCameraId(): Int {         val cameraInfo = Camera.CameraInfo()         //遍历所有的Camera id,比较CameraInfo facing          for (id in 0 until Camera.getNumberOfCameras()) {             Camera.getCameraInfo(id, cameraInfo)             if (cameraInfo.facing == cameraFacing) {                 return id             }         }         return 0     }
  3.打开 Camera 获取 Camera 对象 /** *获取Camera 实例 */  private fun getCameraInstance(id: Int): Camera {       return try {         //调用Camera的open函数获取Camera的实例           Camera.open(id)       } catch (e: Exception) {           throw IllegalAccessError("Camera not found")       }   }
  4.设置 Camera 的相关参数 //[3]设置参数 val parameters = cameraInstance!!.parameters          if (parameters.supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {             parameters.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE         }         cameraInstance!!.parameters = parameters
  5.设置 previewDisplay //【4】  调用Camera API 设置预览Surface         surfaceHolder?.let { cameraInstance!!.setPreviewDisplay(it) }
  6.设置预览回调 //【5】 调用Camera API设置预览回调         cameraInstance!!.setPreviewCallback { data, camera ->             if (data == null || camera == null) {                 return@setPreviewCallback             }             val size = camera.parameters.previewSize             onPreviewFrame?.invoke(data, size.width, size.height)         }
  7.开启预览 //【6】 调用Camera API开启预览         cameraInstance!!.startPreview()
  上面代码中的【3】【4】【5】【6】都是调用 Camera 类的 API 来完成,
  经过上面的流程之后,Camera 的预览会显示在传入的 Surface 上,并且在 Camera 停止前会一直回调函数 onPreviewFrame(byte[] data,Camera camera)  ,其中 byte[] data 中存储的就是实时的 YUV 图像数据。 byte[] data 的格式是 YUV 格式中的 NV21
  【腾讯文档】FFmpegWebRTCRTMPRTSPHLSRTP播放器-音视频流媒体高级开发-资料领取FFmpegWebRTCRTMPRTSPHLSRTP鎾斁鍣�-闊宠棰戞祦濯掍綋楂樼骇寮€鍙�-璧勬枡棰嗗彇
  YUV 图像格式色彩空间
  这里我们只讲常用到的两种色彩空间。
  RGB RGB 的颜色模式应该是我们最熟悉的一种,在现在的电子设备中应用广泛。通过 R G B 三种基础色,可以混合出所有的颜色。
  YUV  这里着重讲一下 YUV,这种色彩空间并不是我们熟悉的。这是一种亮度与色度分离的色彩格式。
  早期的电视都是黑白的,即只有亮度值,即 Y。有了彩色电视以后,加入了 UV 两种色度,形成现在的 YUV,也叫 YCbCr。
  Y:亮度,就是灰度值。除了表示亮度信号外,还含有较多的绿色通道量。
  U:蓝色通道与亮度的差值。
  V:红色通道与亮度的差值。
  采用 YUV 有什么优势呢?
  人眼对亮度敏感,对色度不敏感,因此减少部分 UV 的数据量,人眼却无法感知出来,这样可以通过压缩 UV 的分辨率,在不影响观感的前提下,减小视频的体积。 RGB 和 YUV 的换算
  Y = 0.299R + 0.587G + 0.114B
  U = -0.147R - 0.289G + 0.436B
  V = 0.615R - 0.515G - 0.100B
  ——————————————————
  R = Y + 1.14V
  G = Y - 0.39U - 0.58V
  B = Y + 2.03U YUV 格式
  YUV 存储方式分为两大类:planar 和 packed。 planar:先存储所有 Y,紧接着存储所有 U,最后是 V;
  packed:每个像素点的 Y、U、V 连续交叉存储。
  pakced 存储方式已经非常少用,大部分视频都是采用 planar 存储方式。
  对于 planar 存储方式,通过省略一些色度信息,即亮度共用一些色度信息,进而节省存储空间。因此,planar 又区分了以下几种格式:  YUV444、 YUV422、YUV420。
  YUV 4:4:4 采样,每一个 Y 对应一组 UV 分量。
  YUV 4:2:2 采样,每两个 Y 共用一组 UV 分量。
  YUV 4:2:0 采样,每四个 Y 共用一组 UV 分量。
  其中,最常用的就是  YUV420 。
  YUV420 格式存储方式又分两种类型 YUV420P:三平面存储。数据组成为 YYYYYYYYUUVV(如 I420)或 YYYYYYYYVVUU(如 YV12)。 YUV420SP:两平面存储。分为两种类型 YYYYYYYYUVUV(如 NV12)或 YYYYYYYYVUVU(如 NV21) Camera2
  在 Andorid SDK API 21 之后呢,Google 就推荐使用 Camera2 体系来管理设备,Camera2 还是与 Camera1 有很大的不同的。一样的,我们先来看一下 Camera2 体系的部分类图。
  Camera2 要比 Camera1 复杂的多,CameraManager CameraCaptureSession 是 Camera2 体系的核心类,CameraManager 用来管理摄像头的打开和关闭 Camera2 引入了 CameraCaptureSession 来管理拍摄会话。
  我们下面来看一下更详细的流程图。
  1.在开启 Camera 之前先释放 Camera,这一步的目的是重置 Camera 的状态 private fun releaseCamera() {         imageReader?.close()         cameraInstance?.close()         captureSession?.close()         imageReader = null         cameraInstance = null         captureSession = null     }
  2.获取 Camera 的 Id /**   *【1】 获取Camera Id   */     private fun getCameraId(facing: Int): String? {         return cameraManager.cameraIdList.find { id ->             cameraManager.getCameraCharacteristics(id).get(CameraCharacteristics.LENS_FACING) == facing         }     }
  3.打开 Camera try {           //【2】打开Camera,传入的 CameraDeviceCallback()是摄像机设备状态回调             cameraManager.openCamera(cameraId, CameraDeviceCallback(), null)         } catch (e: CameraAccessException) {             Log.e(TAG, "Opening camera (ID: $cameraId) failed.")         }  //设备状态回调     private inner class CameraDeviceCallback : CameraDevice.StateCallback() {         override fun onOpened(camera: CameraDevice) {             cameraInstance = camera             //【3】开启拍摄会话             startCaptureSession()         }          override fun onDisconnected(camera: CameraDevice) {             camera.close()             cameraInstance = null         }          override fun onError(camera: CameraDevice, error: Int) {             camera.close()             cameraInstance = null         }     }
  4.开启拍摄会话  //【3】开启拍摄会话   private fun startCaptureSession() {         val size = chooseOptimalSize()         //创建ImageRender并设置回调         imageReader =                 ImageReader.newInstance(size.width, size.height, ImageFormat.YUV_420_888, 2).apply {                     setOnImageAvailableListener({ reader ->                         val image = reader?.acquireNextImage() ?: return@setOnImageAvailableListener                         onPreviewFrame?.invoke(image.generateNV21Data(), image.width, image.height)                         image.close()                     }, null)                 }          try {             if (surfaceHolder == null) {               //设置ImageRender的surface给cameraInstance,以便后面预览的时候数据呈现到ImageRender的surface,从而触发ImageRender的回调                 cameraInstance?.createCaptureSession(                         listOf(imageReader!!.surface),                         //【4】CaptureStateCallback是CameraCaptureSession的内部类,是摄像机会话状态的回调                         CaptureStateCallback(),                         null                 )             } else {                 cameraInstance?.createCaptureSession(                         listOf(imageReader!!.surface,                                 surfaceHolder!!.surface),                         CaptureStateCallback(),                         null                 )             }          } catch (e: CameraAccessException) {             Log.e(TAG, "Failed to start camera session")         }     }    //摄像机会话状态的回调     private inner class CaptureStateCallback : CameraCaptureSession.StateCallback() {         override fun onConfigureFailed(session: CameraCaptureSession) {             Log.e(TAG, "Failed to configure capture session.")         }     //摄像机配置完成         override fun onConfigured(session: CameraCaptureSession) {             cameraInstance ?: return             captureSession = session             //设置预览CaptureRequest.Builder             val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)             builder.addTarget(imageReader!!.surface)             surfaceHolder?.let {                 builder.addTarget(it.surface)             }              try {               //开启会话                 session.setRepeatingRequest(builder.build(), null, null)             } catch (e: CameraAccessException) {                 Log.e(TAG, "Failed to start camera preview because it couldn"t access camera", e)             } catch (e: IllegalStateException) {                 Log.e(TAG, "Failed to start camera preview.", e)             }         }     }PS
  ImageRender 可以直接访问呈现在 Surface 上得图像数据,ImageRender 的工作原理是创建实例并设置回调,这个回调会在 ImageRender 所关联的 Surface 上的图像可用时调用
  我们分析了上面的 Camera 采集数据,完整的代码请看文末的 Github 地址。 AudioRecord
  上面分析完了视频,我们接着来看音频,录音 API 我们使用 AudioRecord,录音的流程相对于视频而言要简单许多,一样的,我们先来看一下简单类图。
  就一个类,API 也简单明了,我们来看一下流程。
  下面上代码    public void startRecord() {    //开启录音         mAudioRecord.startRecording();         mIsRecording = true;         //开启新线程轮询         ExecutorService executorService = Executors.newSingleThreadExecutor();         executorService.execute(new Runnable() {             @Override             public void run() {                 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE_IN_BYTES];                 while (mIsRecording) {                     int len = mAudioRecord.read(buffer, 0, DEFAULT_BUFFER_SIZE_IN_BYTES);                     if (len > 0) {                         byte[] data = new byte[len];                         System.arraycopy(buffer, 0, data, 0, len);                         //处理data                     }                 }             }         });      }       public void stopRecord() {         mIsRecording = false;         mAACMediaCodecEncoder.stopEncoder();         mAudioRecord.stop();     }
  AudioRecord 生成的 byte[] data 即 PCM 音频数据。 小结
  本章我们对音视频的原生输入 API 进行了详细的介绍,这个也是我们后面博客的基础,有了 YUV 和 PCM 数据之后,就可以编码了,下一篇我们再来分析 MediaCodec,用 MediaCodec 对原生音视频数据进行硬编码生成 Mp4。
  作者:声网
  链接:https://juejin.cn/post/7021152032220250142

56岁香港性感女神近况曝光!坐地铁逛小巷接地气,身材臃肿想复出美人风骨,其实更多的还是一种自信吧!毕竟美是有着千万种形态的,并不局限于任何一种。近日,香港性感女神李丽珍在社交平台晒个人近况,56岁的穿着休闲,穿梭于香港的大街小巷却无人认识她,突发!前港姐拍新剧疑被收音师非礼!发长文揭露他扯开我裙子昨日TVB艺人麦皓儿(Cayley)在社交平台爆料拍摄TVB新剧下流上车族时,怀疑被当日的收音师(Eric)非礼,麦皓儿表示绝不哑忍,所以在网上公开事件的来龙去脉。麦皓儿发文称当日08年汶川地震中的女警,废墟中哺乳9个婴儿,时隔多年被跨4级提拔我只是一名普通的警察,也只是一名平凡的母亲。追溯到2008年5月12日14时28分4秒汶川大地震时期的画面,无数高楼大厦尽数摧毁,万家灯火瞬间被破坏。彼时,全世界的目光聚焦到了无情最新!高考期间,三明这些路段施行交通管制十年寒窗磨一剑只待今朝问鼎时明天(6月7日)我们将迎来2022年高考为了给莘莘学子营造良好的考试环境明天,三明全市高考考点周边将实行交通管制出行的人赶紧看过来!下面这些事情很重要敬不看不行,这可是她成年后的第一部电视剧啊!梦华录定档开播啦!一口气放送8集呀!好过瘾呐!精良的服化道。了不得的卡司阵容。剧集根据关汉卿元杂剧赵盼儿风月救风尘改编,主要讲的是钱塘女子赵盼儿因为高中探花的未婚夫要弃她另娶,清华700多门课,采用全英文授课,是与国际接轨还是追捧英文文萌妈教育日记随着中国在国际地位上的提高,我国教育领域对英文课程的争议也越来越大。学习英语到底有没有必要,成为了很多家长的争论点。近8成的家长认为英语无用,理由很直接,学生们毕业后请注意,本文高能!罗列几款能让你两眼成灯泡到天明的咖啡还记得较早的时候有和大家推荐过全球最提神的咖啡,那就是死亡之愿。这个听起来都有点不寒而栗的名字,是一家来自纽约RoundLake的小公司,创始人MichaelBrown。其品牌包装蹲一会儿突然站起来,眼冒金星,并非是低血糖?早了解早受益或许你曾经也有这样的感受,蹲久后忽然站立起来,会发生头晕目眩,眼花,两脚酸软,两眼发黑的状况,一不小心还会跌倒在地上。关于这种情况,很多人觉得是蹲时间久了腿麻造成的,更有人觉得自己非诚勿扰女嘉宾王佳,牵手富二代惨遭抛弃,闪婚后却残忍弑夫我曾经以为我是一个为爱可以走天涯的人,我认为有爱的地方就是天堂。但我不知道为什么找这份爱找了这么久,我真的找得很辛苦。我真的很想去幸福,也让我的所有亲人都为我放心。我不管他是生老病会这样回你消息的人,一定很爱你作家刘延说世上最美的情书,就是聊天记录,不信你翻阅你的聊天记录,最值得你深爱的人,聊天信息一定是温暖满满,也是走得最长久的人。的确,根据微信聊天的内容,可以看出一个人对你的态度。因40年老渔民这5种海鱼都是野生的,无法养殖营养高,遇到别错过如果你也喜欢美食,点击关注,每天不断更新精彩内容!导语40年老渔民这5种海鱼都是野生的,无法养殖营养高,遇到别错过!随着社会的进步跟发展,人们的生活水平以及生活质量都得到一定的提高
雷军真的很猛索尼IMX7665000mAh,256GB存储跌至1529元近年来,手机市场的竞争越来越激烈,每个品牌都在竭力追求创新,让消费者体验更出色的手机。消费者不仅对性能要求更高了,拍照体验也越来越获得众人的期待,在这个背景下,RedmiNote1中国十大毒野果,你家乡能见到几种?在野外我们经常会看到一些野生的果子,对于农村留守儿童和外出旅游的城里人来说这些果子有的看起来颜色鲜艳,外形漂亮,十分诱人,但并不是所有的野果都是能吃的,这个时候我们就不能被它们的外珠穆朗玛峰一半尼泊尔,一半中国,主权属于谁?看完终于明白了珠穆朗玛峰是世界的最高点,位于中国与尼泊尔边界上。珠穆朗玛峰是藏语的叫法,意为女神峰,只是年代久远,很少人知道这个典故了。珠峰从远处看呈巨型金字塔状,地形极端险峻,环境非常复杂。从防沉迷,还需联手共治防未成年人沉迷网络,不光是个行业问题,也是个社会问题,不只是个技术问题,也是个教育问题,离不开平台家长学校监管部门等多方共治,这已成社会常识。既然要的是社会共治架构下的各尽本分,那以治理创新呵护消费信心一年一度的315前后,消费维权再次成为舆论关注的热点。从全国各地公布的消费维权典型案例来看,其中既有产品质量参差不齐缺斤短两售后服务缺失等老毛病,也有直播售假价格刺客密室逃脱安全隐应该提供武器甚至直接派兵支持俄罗斯吗?中俄不是同盟国,但从某种角度看,又胜似同盟国。按中方的说法,中俄合作上不封顶,没有禁区没有止境。俄方的说法则是,两国超出了一般国与国之间的友好关系,比军事同盟还要同盟。俄乌冲突爆发摘下金伞伞走上致富路最是一年春好处,万物复苏生机来。眼下,安康市石泉县池河镇五爱村集体股份经济合作社的羊肚菌喜获丰收,一朵朵鲜嫩的羊肚菌撑起褐色的小伞,如雨后春笋般破土而出,错落有致地盛开在菌垄上,沁白沙街成夜归女孩梦魇,56岁超市大姐留灯11年,守护她们回家长沙天心区白沙街隐隐的藏在闹市之中,巷外是灯红酒绿的商圈巷内是古朴单调的老街。11年前,这里的路灯很少,巷外的热闹走不进来,巷内的寂静溢不出去。每到夜深人静,簌簌的风声和无尽的黑暗焦点丨制造业韧性回升高端制造动力强劲图片来源新华社中国经济时报记者童彤任何时候中国都不能缺少制造业。习近平总书记在全国两会期间参加江苏代表团审议时的这番话,言简意赅发人深省。在三年疫情冲击和影响下,我国制造业顶住重重张家界新娘多人约P后续来了新郎回应和我们想象的不一样01hr婚前约炮,还要三人运动,张家界新娘事件最终消息来了,新郎通过社交平台,宣布离婚后打点滴治疗熬过就好了!前一阵毁三观的张家界新娘事件,正式终结。事情的发展是从新娘和小白龙聊天铃芽之旅在北大举办首映礼,新海诚邀你奔赴春之约!电影正式上映我一定要再去看一次!是一场治愈之旅,感到过去的阴霾都被融化了。走出玲芽之旅的首映礼现场,北京大学的同学们纷纷感慨。3月17日,新海诚导演最新集大成之作铃芽之旅在北京大学