android rtsp 拉流h264 h265,解碼nv12轉碼nv21耗時卡頓問題及ffmpeg優化

一、 背景介紹及問題概述

? ? ?項目需求需要在rk3568開發板上面,通過rtsp協議拉流的形式獲取攝像頭預覽,然后進行人臉識別 姿態識別等后續其它操作。由于rtsp協議一般使用h.264 h265視頻編碼格式(也叫 AVC 和 HEVC)是不能直接用于后續處理,需要先解碼后獲取解碼后的數據,一般解碼后的數據格式為:YUV420P?或者 ?NV12 格式,YUV420P格式轉碼的可以參考libyuv庫?,這邊測試設備是大華攝像頭解碼后格式為NV12格式,所有是以NV12轉碼NV21(也稱之為:裸碼或原始數據) ,Android 手機相機預覽幀就是NV21格式。

先看NV21 輸出優化后的效果 55 -58秒? 4秒視頻,gif限制5M? 僅參考效果即可

二、 RTSP 拉流基礎與編碼格式簡述

RTSP 協議在 Android 中的應用:
RTSP 是用于控制流媒體播放的協議,Android 中常通過 FFmpeg 或 GStreamer 實現視頻流的接入與播放,適用于實時監控、遠程視頻等場景。

H264 與 H265 編碼格式區別
H265 相比 H264 壓縮效率更高,支持更高分辨率的視頻傳輸,但解碼開銷更大,對性能要求更高,尤其在移動端解碼時容易造成卡頓。

FFmpeg 在拉流中的角色
FFmpeg 負責解析 RTSP 協議、解封裝音視頻流,并將其解碼為原始幀數據,為后續圖像處理與播放提供底層支持。

NV12
YUV420 格式,Y 分量后緊跟交錯排列的 UV 分量,常用于解碼輸出。

NV21
YUV420 格式,Y 分量后緊跟交錯排列的 VU 分量,Android 攝像頭默認輸出格式。

三、RTSP 拉流

? ? ? ? 網上常見Android拉流第三方庫:VLC for Android / LibVLC? ? ?FFmpeg / FFmpegKit / FFmpeg Android Java?FFmpeg ?FFmpeg / FFmpegKit / FFmpeg Android Java? ??ExoPlayer + RTSP 擴展? ?GStreamer for Android
簡單說一下上面幾個庫的情況,第一個庫適用快速播放,第四個庫對Android支持不太友好,所以在第二和第三個之中挑選,由于也是第一次接觸rtsp協議,選擇簡單易于集成和官方庫第三個ExoPlayer + RTSP,嘗試直接通過rtsp協議獲取攝像頭預覽數據,單從播放流暢度來看,效果還是很不錯的,但是由于rtsp協議獲取到是壓縮后的數據格式H264.H.265,并不能滿足項目需求。

下面是一個?AndroidX Media ExoPlayer?demo示例參考地址:AndroidX Media ExoPlayer

四、RTSP? ?H.264、H.265 解碼、轉碼和出現的問題

RTSP拉流之后H.264 還需要java層通過MediaCodec硬解碼獲取NV12,在轉碼格式NV21 后傳入YuvImage 構造 Bitmap,再繪制到 Canvas 上這種形式效率很低,單純解碼NV12需要32毫秒左右,在轉碼NV21到Canvas顯示更是需要100毫秒,這種就會在預覽界面上看到明顯的延遲。
然后感覺java層rtsp協議讀取 轉碼確實效率比較低,哪怕是另開線程轉碼也不能解決效率問題。

只能想在C層處理了,那就有前面提到的FFmpeg庫,但是這個開源庫需要自己搭建linux/ubuntu環境編譯出來so和源碼,在集成到Android項目。必要時可以考慮自己編譯,網上有很多教程~
直接找了一個比較貼近項目需求的FFmpeg解碼轉碼nv21的項目,拉下來后運行后發現畫面拉伸,卡頓掉幀嚴重,而且FFmpeg版本比較老舊。
https://github.com/1244975831/RtmpPlayerDemo

下面是已經編譯的 FFmpeg_Android Demo地址:RtmpPlayerDemo
?

五、解決卡頓掉幀 拉伸問題

優化前FFmpeg拉流源碼

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmpplaydemo_RtmpPlayer_nativeStart(JNIEnv *env, jobject) {//開始播放stop = false;if (frameCallback == NULL) {return;}// 讀取數據包int count = 0;while (!stop) {if (av_read_frame(pFormatCtx, pPacket) >= 0) {//解碼int gotPicCount = 0;int decode_video2_size = avcodec_decode_video2(pCodecCtx, pAvFrame, &gotPicCount,pPacket);LOGI("decode_video2_size = %d , gotPicCount = %d", decode_video2_size, gotPicCount);LOGI("pAvFrame->linesize  %d  %d %d", pAvFrame->linesize[0], pAvFrame->linesize[1],pCodecCtx->height);if (gotPicCount != 0) {count++;sws_scale(pImgConvertCtx,(const uint8_t *const *) pAvFrame->data,pAvFrame->linesize,0,pCodecCtx->height,pFrameNv21->data,pFrameNv21->linesize);//獲取數據大小 寬高等數據int dataSize = pCodecCtx->height * (pAvFrame->linesize[0] + pAvFrame->linesize[1]);LOGI("pAvFrame->linesize  %d  %d %d %d", pAvFrame->linesize[0],pAvFrame->linesize[1], pCodecCtx->height, dataSize);jbyteArray data = env->NewByteArray(dataSize);env->SetByteArrayRegion(data, 0, dataSize,reinterpret_cast<const jbyte *>(v_out_buffer));// onFrameAvailable 回調jclass clazz = env->GetObjectClass(frameCallback);jmethodID onFrameAvailableId = env->GetMethodID(clazz, "onFrameAvailable", "([B)V");env->CallVoidMethod(frameCallback, onFrameAvailableId, data);env->DeleteLocalRef(clazz);env->DeleteLocalRef(data);}}av_packet_unref(pPacket);}
}

優化后FFmpeg拉流源碼

extern "C"
JNIEXPORT void JNICALL
Java_com_natives_lib_RtmpPlayer_nativeStart(JNIEnv *, jobject) {isPlaying = true;// 啟動解碼和渲染線程pthread_create(&decodeThread, nullptr, decodeFunc, nullptr);pthread_create(&renderThread, nullptr, renderFunc, nullptr);AVRational timeBase = formatCtx->streams[0]->time_base;while (isPlaying && av_read_frame(formatCtx, packet) >= 0) {if (packet->stream_index != 0) {av_packet_unref(packet);continue;}AVPacket *pktCopy = av_packet_alloc();if (!pktCopy) {av_packet_unref(packet);continue;}av_packet_ref(pktCopy, packet);// 使用 packet->pts 或 dts(回退方案),并轉換為微秒int64_t pts = (packet->pts != AV_NOPTS_VALUE) ? packet->pts : packet->dts;if (pts == AV_NOPTS_VALUE) {// 最后兜底:使用系統時間(非推薦)pts = av_gettime();} else {pts = av_rescale_q(pts, timeBase, {1, 1000000}); // 轉換為微秒單位}pktCopy->pts = pts;// 清理隊列中過期幀{std::unique_lock<std::mutex> lock(queueMutex);while (!packetQueue.empty()) {AVPacket *front = packetQueue.front();int64_t frontPts = front->pts;if (av_gettime() - frontPts > MAX_QUEUE_TIME * 1000000) {av_packet_unref(front);packetQueue.pop();} else {break;}}// 判斷是否可入隊(關鍵幀優先)if ((packet->flags & AV_PKT_FLAG_KEY) || packetQueue.size() < MAX_QUEUE_SIZE) {packetQueue.push(pktCopy);queueCond.notify_one();} else {av_packet_unref(pktCopy); // 丟棄非關鍵幀}}av_packet_unref(packet);}// 通知線程退出isPlaying = false;queueCond.notify_all();pthread_join(decodeThread, nullptr);pthread_join(renderThread, nullptr);
}

優化前:單線程處理拉流、解碼、渲染,所有處理都在單線程處理,無時間戳控制,播放幀不判斷時效性,可能導致延遲累積或卡頓所有幀無差別處理,avcodec_decode_video2較舊 API效率低。


優化后:使用多線程(拉流、解碼、渲染分離)提升并發效率,使用 packet->pts 轉換為微秒進行幀時間控制時間,增加隊列管理邏輯、清理超時幀、控制最大緩存避免內存堆積,關鍵幀優先入隊丟棄非關鍵幀,保障解碼連續性和渲染提高播放流暢度。


注:
這邊還有一個問題,在視頻流分辨率在2688*1520下,在3568開發板下只有每秒9幀(輸入源每秒25幀),在1920*1080分辨率下有每秒18幀,1280*720分辨率則是? 每秒25幀。
市面上主流手機則不存在這種問題,在2688*1520下也可以跑滿25幀。

?

六、cmake編譯問題

? ? ? 一開始是在app里面直接編譯so庫,沒有其它問題。當我把編譯源碼相關文件拉到本地依賴庫時,就會找不到編譯的so庫。感覺很奇妙的問題,花了幾個小時才找到原因,還是遭了熟練度的坑。
流程:新建lib庫,在app里依賴lib,然后直接調用lib內的native


1.剛開始以為是so沒有編譯成功,但是在build里可以找到生成的so。
2 .考慮到是否生成so沒有打入apk問題,所以一直找不到。但是直接依賴lib是直接合并到APK 或 AAB 中 dex和lib庫的,會默認合并so庫才對哇。
3.直接查看apk包內的lib庫

確實有cpp生成的so庫,但是怎么有多出來了x86,arm64-v8a這些庫呢?在lib模塊里面指定了一個armeabi-v7a架構,在app里面也沒有其它編譯so的cmake文件。
只能猜測是在app第三方依賴庫里面,然后直接把依賴庫丟到gpt排查,經過測試果然是

上面這個第三方依賴生成的 libimage_processing_util jni.so庫

問題找到了,那就好解決了

方案一
在app內同步指定 ndk 為??armeabi-v7a 架構


方案二
去掉或替換生成多余的第三方依賴庫


附demo連接:?Demo

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

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

相關文章

運維面試題(十四)

6.將日志從一臺服務器保存到另一臺服務器中的方法 1.使用 rsync 同步日志文件 2.使用 scp 手動或腳本化傳輸 3.配置日志服務&#xff08;如 syslog 或 rsyslog &#xff09;遠程傳輸 ? 4.編寫腳本定時上傳&#xff1a;結合 cron 定時任務和傳輸工具&#xff0c;編…

永磁同步電機控制中,滑模觀測器是基于反電動勢觀測轉子速度和角度的?擴展卡爾曼濾波觀測器是基于什么觀測的?擴展卡爾曼濾波觀測器也是基于反電動勢嗎?

滑模觀測器在PMSM中的應用&#xff1a; 滑模觀測器是一種非線性觀測器&#xff0c;利用切換函數設計&#xff0c;使得狀態估計誤差迅速趨近于零&#xff0c;實現快速響應和對外部干擾的魯棒性。 在永磁同步電機&#xff08;PMSM&#xff09;無傳感器控制中&#xff0c;滑模觀測…

【前端】Vue一本通 ESLint JSX

近幾天更新完畢&#xff0c;不定期持續更新&#xff0c;建議關注收藏點贊。 目錄 工具推薦vscode插件vue-devtoolsESLint JSX語法擴展簡介設計模式快速入門 vue/cli腳手架使用vue指令 工具推薦 工欲善其事&#xff0c;必先利其器。 vscode插件 Vetur&#xff1a;vue代碼高亮…

【adb】bat批處理+adb 自動亮屏,自動解鎖屏幕,啟動王者榮耀

準備adb 下載 需要確認是否安裝了adb.exe文件,可以在: 任務管理器 -->詳細信息–>找一下后臺運行的adb 安裝過anroid模擬器,也存在adb,例如:雷電安裝目錄 D:\leidian\LDPlayer9 單獨下載adb 官方下載地址:[官方網址] 下載目錄文件: 測試adb USB連接手機 首先在設置界…

微信小程序轉為App實踐篇 FinClip

參考下面鏈接先 開始實踐 微信小程序轉為App并上架應用市場_微信小程序生成app-CSDN博客 首先在FinClip 官網上下載應用 小程序開發工具下載_小程序sdk下載資源-FinClip資源下載|泰坪小程序開放平臺 下載到本地安裝 打開導入自己的小程序項目&#xff1b;導入時會解析自己的…

arco design框架中的樹形表格使用中的緩存問題

目錄 1.問題 2.解決方案 1.問題 arco design框架中的樹形表格使用中的緩存問題&#xff0c;使用了樹形表格的load-more懶加載 點擊展開按鈕后&#xff0c;點擊關閉&#xff0c;再次點擊展開按鈕時&#xff0c;沒有調用查詢接口&#xff0c;而是使用了緩存的數據。 2.解決方…

100個GEO基因表達芯片或轉錄組數據處理023.GSE24807

100個GEO基因表達芯片或轉錄組數據處理 寫在前邊 雖然現在是高通量測序的時代&#xff0c;但是GEO、ArrayExpress等數據庫儲存并公開大量的基因表達芯片數據&#xff0c;還是會有大量的需求去處理芯片數據&#xff0c;并且建模或驗證自己所研究基因的表達情況&#xff0c;芯片…

SAP ECCS標準報表在報表中不存在特征CG細分期間 消息號 GK715報錯分析

ECCS報表執行報錯&#xff1a; 在報表中不存在特征CG細分期間 消息號 GK715 診斷 未在報表中指定特征CG細分期間。但是&#xff0c;同時需要特征CG細分期間和其它特征。例如&#xff1a; 期間’需要用于擴展合并組。 系統響應 處理終止 步驟 調整報表定義。 報這個錯。 業務背景…

spring boot 文件下載

1.添加文件下載工具依賴 Commons IO is a library of utilities to assist with developing IO functionality. <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version> </depe…

FastAPI 中定義接口函數參數,包含請求體參數、查詢參數、依賴注入參數的組合

FastAPI 中定義接口函數參數&#xff0c;包含請求體參數、查詢參數、依賴注入參數的組合。 ? 示例結構 async def chat(request: Request,data: ChatData,conversation_id: Optional[str] Query(None),current_user: User Depends(get_current_user), ):這表示你定義了一個…

實用類題目

1. 密碼強度檢測 題目描述&#xff1a;生活中&#xff0c;為保證賬戶安全&#xff0c;密碼需要有一定強度。編寫一個方法&#xff0c;接收一個字符串作為密碼&#xff0c;判斷其是否符合以下強度要求&#xff1a;長度至少為 8 位&#xff0c;包含至少一個大寫字母、一個小寫字…

MATLAB學習筆記(二) 控制工程會用到的

MATLAB中 控制工程會用到的 基礎傳遞函數表達傳遞函數 零極點式 狀態空間表達式 相互轉化畫響應圖線根軌跡Nyquist圖和bode圖現控部分求約旦判能控能觀極點配置和狀態觀測 基礎 傳遞函數表達 % 拉普拉斯變換 syms t s a f exp(a*t) %e的a次方 l laplace(f) …

基于YOLOv9的課堂行為檢測系統

基于YOLOv9的課堂行為檢測系統 項目概述 本項目是一個基于YOLOv9深度學習模型的課堂行為檢測系統&#xff0c;旨在通過計算機視覺技術自動識別和監測課堂中學生的各種行為狀態&#xff0c;幫助教師更好地了解課堂教學效果。 項目結構 課堂行為檢測/ ├── data/ │ ├──…

C 語言中的 volatile 關鍵字

1、概念 volatile 是 C/C 語言中的一個類型修飾符&#xff0c;用于告知編譯器&#xff1a;該變量的值可能會在程序控制流之外被意外修改&#xff08;如硬件寄存器、多線程共享變量或信號處理函數等&#xff09;&#xff0c;因此編譯器不應對其進行激進的優化&#xff08;如緩存…

java 洛谷題單【算法2-1】前綴和、差分與離散化

P8218 【深進1.例1】求區間和 解題思路 前綴和數組&#xff1a; prefixSum[i] 表示數組 a 的前 (i) 項的和。通過 prefixSum[r] - prefixSum[l - 1] 可以快速計算區間 ([l, r]) 的和。 時間復雜度&#xff1a; 構建前綴和數組的時間復雜度是 (O(n))。每次查詢的時間復雜度是 …

綠盟二面面試題

5000篇網安資料庫https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39a6eab17cc0ed0fca5f0e4c979ce64bd112762def9ee7cf0112a7e76af&scene21#wechat_redirect 1. 原理深度&…

線程安全學習

1 什么是線程 線程是cpu調度的最小單位&#xff0c;在Linux 下 實現線程的方式為輕量級進程&#xff0c;復用進程的結構體&#xff0c;使用clone函數創建 2 線程安全 所謂線程安全&#xff0c;更確切的應該描述為內存安全 #include <stdio.h> #include <pthread.h…

Linux紅帽:RHCSA認證知識講解(十 三)在serverb上破解root密碼

Linux紅帽&#xff1a;RHCSA認證知識講解&#xff08;十 三&#xff09;在serverb上破解root密碼 前言操作步驟 前言 在紅帽 Linux 系統的管理工作中&#xff0c;系統管理員可能會遇到需要重置 root 密碼的情況。本文將詳細介紹如何通過救援模式進入系統并重新設置 root 密碼。…

**Microsoft Certified Professional(MCP)** 認證考試

1. MCP 認證考試概述 MCP&#xff08;Microsoft Certified Professional&#xff09;是微軟認證體系中的一項入門級認證&#xff0c;旨在驗證考生在微軟產品和技術&#xff08;如 Windows Server、Azure、SQL Server、Microsoft 365&#xff09;方面的技能。2020 年&#xff0…

系統性能優化總結與思考-第一部分

1.C代碼優化策略總結 編譯器方面&#xff1a;用好的編譯器并用好編譯器&#xff08;支持C11的編譯器&#xff0c;IntelC&#xff08;速度最快&#xff09;GNU的C編譯器GCC/G&#xff08;非常符合標準&#xff09;&#xff0c;Visual C&#xff08;性能折中&#xff09;&#x…