記錄一次Android推流、錄像踩坑過程

背景:

? ? ? ? 按照需求,需要支持APP在手機息屏時進行推流、錄像。

技術要點:

????????1、手機在息屏時能夠打開camera獲取預覽數據

????????2、獲取預覽數據時進行編碼以及合成視頻

一、息屏時獲取camera預覽數據:
? ? ? ? ①Camera.setPreviewDisplay(SurfaceHolder holder):

一般常規的打開camera后(Camera.open(int cameraId)),給相機設置預覽setPreviewDisplay(SurfaceHolder holder),holder通過surfaceview獲取。但是者在surfaceDestroyed(xxxxxx)后無法獲取預覽數據,所以setPreviewDisplay(SurfaceHolder holder)此方法無法滿足息屏的需求。

????????②Camera.setPreviewTexture(SurfaceTexture surfaceTexture):

此方法通過創建一個new?SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)傳入就可以實現息屏獲取相機的預覽數據。這樣就可以避免直接使用TextureView帶來的onSurfaceTextureDestroyed(xxxx)導致息屏后無法獲取預覽數據。

二、預覽camera預覽數據:
? ? ? ? ①Camera.setPreviewTexture(SurfaceTexture surfaceTexture):

獲取到yuv數據進行轉換成bitmap,然后用Imageview或者Surfaceview直接顯示。

此方法帶來的弊端:

? ? ? ? 1、每一幀數據都要生成bitmap,短時間頻繁的創建對象會導致STW,從而導致ANR

? ? ? ? 2、預覽數據不流暢,是用Imageview或者Surfaceview手動方式展示的

? ? ? ? ②Camera.setPreviewDisplay(SurfaceHolder holder):

此方法是Android自帶的,沒有上述的弊端:ANR、畫面卡頓,但是在息屏時無法獲取預覽數據

? ? ? ? ③Camera.setPreviewTexture(SurfaceTexture ????????????????surfaceTexture)+Camera.setPreviewDisplay(SurfaceHolder holder):

此方法既解決了預覽問題也解決了息屏獲取預覽數據問題,但是此方法在MediaMuxer兩種模式轉換合成音視頻時無法合成連續的音視頻,只能亮屏時合成一段,息屏時合成一段。不過也嘗試在轉換模式時,MediaMuxer繼續寫入數據,雖然視頻可以播放但是會導致寫入失敗,視頻畫面卡頓在轉換的那一幀畫面。因為在轉換模式時,編碼的數據出問題了,大小比之前的要小很多,此問題待研究

三、解決方案:

采用上述的第三種方法:

????????Camera.setPreviewTexture(SurfaceTexture ????????????????surfaceTexture)+Camera.setPreviewDisplay(SurfaceHolder holder);

息屏、切換前后置攝像頭時先釋放相機releaseCamera(),代碼如下:

 override fun releaseCamera() {try {stopBackgroundThread()mCamera?.stopPreview()mCamera?.setPreviewCallbackWithBuffer(null)mCamera?.release()mCamera = null} catch (runError: RuntimeException) {KLog.e(TAG, "releaseCamera happened error: " + runError.message)} catch (e: Exception) {KLog.e(TAG, "releaseCamera error: $e")}}

然后再重新打開相機openCamera,代碼如下:

 override fun openCamera(cameraId: Int,imageFormat: Int,holder: SurfaceHolder?) {mCameraId = cameraIdthis.previewFormat = imageFormatsurfaceHolder = holdermSurfaceTexture = SurfaceTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)openCamera(surfaceHolder, mSurfaceTexture!!, cameraId)}private fun openCamera(surfaceHolder: SurfaceHolder?,surfaceTexture: SurfaceTexture,cameraId: Int) {if (cameraId < 0 /*|| cameraId > Camera.getNumberOfCameras() - 1*/) {Log.w(TAG,"openCamera failed, cameraId=" + cameraId + ", Camera.getNumberOfCameras()=" + Camera.getNumberOfCameras())return}startBackgroundThread()try {
//            Log.i(TAG,"surfaceCreated open camera cameraId=$cameraId start")mCamera = Camera.open(cameraId)mCamera?.setDisplayOrientation(90)if (surfaceHolder == null) {mCamera?.setPreviewTexture(surfaceTexture)} else {mCamera?.setPreviewDisplay(surfaceHolder)}// set preview format @{this.previewFormat = setCameraPreviewFormat(mCamera!!, this.previewFormat)// @}// 設置fps@{val minFps: Int = 30000val maxFps: Int = 30000setCameraPreviewFpsRange(mCamera!!, minFps, maxFps)// @}// 設置預覽尺寸 @{val hasSetPreviewSize = setCameraPreviewSize(mCamera!!)if (hasSetPreviewSize.size > 1) {/* previewWidth = hasSetPreviewSize[0]previewHeight = hasSetPreviewSize[1]GBApp.getInstance().previewWidth = hasSetPreviewSize[0]GBApp.getInstance().previewHeight = hasSetPreviewSize[1]*/previewWidth = 640previewHeight = 480GBApp.instance!!.previewWidth = 640GBApp.instance!!.previewHeight = 480}// @}// 設置照片尺寸 @{setCameraPictureSize(mCamera!!)// @}// 設置預覽回調函數@{mCamera?.setPreviewCallbackWithBuffer(mCameraCallbacks)Log.i(TAG,"ImageFormat: $previewFormat bits per pixel=" + ImageFormat.getBitsPerPixel(previewFormat))// 初始化數組for (index in 0 until previewDataSize) {val previewData = if (previewFormat != ImageFormat.YV12) {ByteArray(previewWidth * previewHeight * ImageFormat.getBitsPerPixel(previewFormat) / 8)} else {val size = ImageUtils.getYV12ImagePixelSize(previewWidth, previewHeight)ByteArray(size)}previewDataArray.add(previewData)}//addAllPreviewCallbackData()mCamera?.addCallbackBuffer(ByteArray(previewWidth * previewHeight * 3 / 2))// @}//autoRatioTextureView()mCamera?.startPreview()} catch (localIOException: IOException) {Log.e(TAG,"surfaceCreated open camera localIOException cameraId=" + cameraId + ", error=" + localIOException.message,localIOException)} catch (run: RuntimeException) {Log.e(TAG,"open camera RuntimeException error=" + run.message)} catch (e: Exception) {Log.e(TAG,"surfaceCreated open camera cameraId=" + cameraId + ", error=" + e.message,e)}}

此情況依舊會導致在切換相機時,出現錄制的視頻卡在某一幀,解決方案如下:

依舊使用SurfaceView預覽相機

1、相機停止寫入數據pauseRecord()

// 根據 status 狀態是否寫入數據
public void pauseRecord() {if (status == Status.RECORDING) {pauseMoment = System.nanoTime() / 1000;status = Status.PAUSED;if (listener != null) listener.onStatusChange(status);}}

2、釋放相機

fun releaseCamera() {try {stopBackgroundThread()mCamera?.stopPreview()mCamera?.setPreviewCallbackWithBuffer(null)mCamera?.release()mCamera = null} catch (runError: RuntimeException) {KLog.e(TAG, "releaseCamera happened error: " + runError.message)} catch (e: Exception) {KLog.e(TAG, "releaseCamera error: $e")}}

3、繼續錄制視頻

fun doResumeRecord(eventData: ResumeRecordEvent) {// 打開相機GBApp.instance?.service?.doOpenCamera(OpenCameraEvent(eventData.holder,VideoTaskUtil.instance.mCameraId,ImageFormat.NV21,eventData.eventType))// 請求關鍵幀camera2Base?.videoEncoder?.requestKeyframe()// 繼續寫入音視頻數據camera2Base?.resumeRecord()}public void resumeRecord() {if (status == Status.PAUSED) {pauseTime += System.nanoTime() / 1000 - pauseMoment;status = Status.RESUMED;if (listener != null) listener.onStatusChange(status);}}

如果合成的視頻在后續還會卡在某一幀,可以把之前的視頻數據隊列清空,這樣避免因為切換相機之前的垃圾數據導致問題,然后執行上面的步驟

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/44461.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/44461.shtml
英文地址,請注明出處:http://en.pswp.cn/web/44461.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

通過 Azure OpenAI 服務使用 GPT-35-Turbo and GPT-4(win版)

官方文檔 Azure OpenAI 是微軟提供的一項云服務&#xff0c;旨在將 OpenAI 的先進人工智能模型與 Azure 的基礎設施和服務相結合。通過 Azure OpenAI&#xff0c;開發者和企業可以訪問 OpenAI 的各種模型&#xff0c;如 GPT-3、Codex 和 DALL-E 等&#xff0c;并將其集成到自己…

input上傳--upload

1.HTML <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>上傳文件</title><link rel"…

《C++并發編程實戰》筆記(一、二)

一、簡介 抽象損失&#xff1a;對于實現某個功能時&#xff0c;可以使用高級工具&#xff0c;也可以直接使用底層工具。這兩種方式運行的開銷差異稱為抽象損失。 二、線程管控 2.1 線程的基本控制 1. 創建線程 線程相關的管理函數和類在頭文件&#xff1a; #include <…

數據結構——線性表(C語言實現)

寫在前面&#xff1a; 在前面C語言的結構體學習中&#xff0c;我提及了鏈表的操作&#xff0c; 學習數據結構我認為還是需要對C語言的數組、函數、指針、結構體有一定的了解&#xff0c;不然對于結構體的代碼可能很難理解&#xff0c;特別是一些書籍上面用的還是偽代碼&#xf…

OpenGL筆記一之基礎窗體搭建以及事件響應

OpenGL筆記一之基礎窗體搭建以及事件響應 總結自bilibili趙新政老師的教程 code review! 文章目錄 OpenGL筆記一之基礎窗體搭建以及事件響應1.運行2.目錄結構3.main.cpp4.CMakeList.txt 1.運行 2.目錄結構 01_GLFW_WINDOW/ ├── CMakeLists.txt ├── glad.c ├── main…

Linux基于centos7指令初學3

date指令 作用&#xff1a; date指令可以查看時間 這個指令可以進行格式化 格式&#xff1a;date %想要的內容 Y&#xff1a;年份 m&#xff1a;月份 d&#xff1a;日 H&#xff1a;時 M&#xff1a;分 S&#xff1a;秒 時間分界線可以由…

GIT相關操作,推送本地分支到遠程倉庫流程記錄學習

git流程 切換到源文件夾&#xff1a;cd 源文件夾克隆遠程倉庫&#xff1a;git clone [ssh]進入項目文件夾&#xff1a;cd .\project\查看本地分支&#xff1a;git branch獲取遠程倉庫更新&#xff0c;使遠程同步&#xff1a;git fetch查看所有分支&#xff08;包括遠程分支&am…

OJ-0712

示例1&#xff1a; input 8 123 124 125 121 119 122 126 123 output 1 2 6 5 5 6 0 0示例2&#xff1a; input 2 95 100 output 1 0示例3&#xff1a; input 2 100 95 output 0 1package com.wsdcode.od;import java.util.Scanner;public class Main {public static void m…

LabVIEW比例壓力控制閥自動測試系統

開發了一套基于LabVIEW編程和PLC控制的比例控制閥自動測試系統。該系統能夠實現共軌管穩定的超高壓供給&#xff0c;自動完成比例壓力控制閥的耐久測試、流量滯環測試及壓力-流量測試。該系統操作簡便&#xff0c;具有高精度和高可靠性&#xff0c;完全滿足企業對自動化測試的需…

安裝jenkins最新版本初始化配置及使用JDK1.8構建項目詳細講解

導讀 1.安裝1.1.相關網址1.2.準備環境1.3.下載安裝 2. 配置jenkins2.1.安裝插件2.2.配置全局工具2.3.系統配置 3. 使用3.1.配置job3.2.構建 提示&#xff1a;如果只想看如何使用jdk1.8構建項目&#xff0c;直接看3.1即可。 1.安裝 1.1.相關網址 Jenkins官網&#xff1a;https…

RabbitMq如何保證消息的可靠性和穩定性

RabbitMq如何保證消息的可靠性和穩定性 rabbitMq不會百分之百讓我們的消息安全被消費&#xff0c;但是rabbitMq提供了一些機制來保證我們的消息可以被安全的消費。 消息確認 消息者在成功處理消息后可以發送確認&#xff08;ACK&#xff09;給rabbitMq&#xff0c;通知消息已…

Hadoop-25 Sqoop遷移 增量數據導入 CDC 變化數據捕獲 差量同步數據 觸發器 快照 日志

章節內容 上節我們完成了如下的內容&#xff1a; Sqoop MySQL遷移到HiveSqoop Hive遷移數據到MySQL編寫腳本進行數據導入導出測試 背景介紹 這里是三臺公網云服務器&#xff0c;每臺 2C4G&#xff0c;搭建一個Hadoop的學習環境&#xff0c;供我學習。 之前已經在 VM 虛擬機…

計算機的錯誤計算(二十九)

摘要 &#xff08;1&#xff09;討論近似值的錯誤數字個數。有時&#xff0c;遇到數字9或0, 不太好確認近似值的錯誤數字個數。&#xff08;2&#xff09;并進一步解釋確認計算機的錯誤計算&#xff08;二十八&#xff09;中一個函數值的錯誤數字個數。 理論上&#xff0c;我…

py2neo常用語句

1.連接數據庫 Neo4j服務器默認的端口號就是7474,所以本地的主機就是"http://localhost:7474" 。 默認的用戶名密碼都是neo4j&#xff0c; # 連接數據庫&#xff0c;輸入個人配置 graph Graph("http://localhost:7474//browser/", auth("neo4j"…

百日筑基第十九天-一頭扎進消息隊列2

百日筑基第十九天-一頭扎進消息隊列2 消息隊列的通訊協議 目前業界的通信協議可以分為公有協議和私有協議兩種。公有協議指公開的受到認可的具有規 范的協議&#xff0c;比如 JMS、HTTP、STOMP 等。私有協議是指根據自身的功能和需求設計的協 議&#xff0c;一般不具備通用性&…

數學建模·熵權法

熵權法 一種計算評價指標之間權重的方法。熵權法是一種客觀的方法&#xff0c;沒有主觀性&#xff0c;比較可靠。 具體定義 熵權法的核心在于計算信息熵&#xff0c;信息熵反映了一個信息的紊亂程度&#xff0c;體現了信息的可靠性 具體步驟 Step1正向化處理 將所以評價指標轉…

智能家居裝修怎么布線?智能家居網絡與開關插座布置

打造全屋智能家居。計劃的智能家居方案以米家系列為主&#xff0c;智能家居聯網方案以無線為主。裝修前為了裝備智能家居做了很多準備工作&#xff0c;本文深圳僑杰智能分享一個智能家居裝修和布線方面的心得與實戰知識。希望能對大家的裝修有所幫助。 ?1.關于網絡 如果房子比…

HTML基本標簽(二)

HTML基本標簽&#xff08;二&#xff09; 表格標簽 table媒體元素audio 音頻vido 視頻 form 表單元素 表格標簽 table <!-- caption 代表表格標題相關屬性border 邊框cellpadding 設置單元格內填充cellspacing 設置單元格間空隙width 設置表格寬度&#xff0c;默認是內容撐…

Python-數據爬取(爬蟲)

~~~理性爬取~~~ 杜絕從入門到入獄 1.簡要描述一下Python爬蟲的工作原理&#xff0c;并介紹幾個常用的Python爬蟲庫。 Python爬蟲的工作原理 發送請求&#xff1a;爬蟲向目標網站發送HTTP請求&#xff0c;通常使用GET請求來獲取網頁內容。解析響應&#xff1a;接收并解析HTTP響…

結合實體類型信息1——基于本體的知識圖譜補全深度學習方法

1 引言 1.1 問題 目前KGC和KGE提案的兩個主要缺點是:(1)它們沒有利用本體信息;(二)對訓練時未見的事實和新鮮事物不能預測的。 1.2 解決方案 一種新的知識圖嵌入初始化方法。 1.3 結合的信息 知識庫中的實體向量表示&#xff0b;編碼后的本體信息——>增強 KGC 2基…