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.mzph.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…