android rtsp 拉流h264 h265,解码nv12转码nv21耗时卡顿问题及ffmpeg优化

news2025/4/18 22:31:48

一、 背景介绍及问题概述

     项目需求需要在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.coloradmin.cn/o/2337580.html

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

相关文章

熊海cms代码审计

目录 sql注入 1. admin/files/login.php 2. admin/files/columnlist.php 3. admin/files/editcolumn.php 4. admin/files/editlink.php 5. admin/files/editsoft.php 6. admin/files/editwz.php 7. admin/files/linklist.php 8. files/software.php 9. files…

DeepSeek 与开源:肥沃土壤孕育 AI 硕果

当 DeepSeek 以低成本推理、多模态能力惊艳全球时&#xff0c;人们惊叹于国产AI技术的「爆发力」&#xff0c;却鲜少有人追问&#xff1a;这份爆发力的根基何在&#xff1f; 答案&#xff0c;藏在中国开源生态二十余年的积淀中。 从倪光南院士呼吁「以开源打破垄断」&#xf…

Maven中clean、compil等操作介绍和Pom.xml中各个标签介绍

文章目录 前言Maven常用命令1.clean2.vaildate3.compile4.test5.package6.verify7.install8.site9.deploy pom.xml标签详解格式<?xml version"1.0" encoding"UTF-8"?>(xml版本和编码)modelVersion&#xff08;xml版本&#xff09;groupId&#xff…

力扣刷题-热题100题-第35题(c++、python)

146. LRU 缓存 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/lru-cache/?envTypestudy-plan-v2&envIdtop-100-liked 双向链表哈希表 内置函数 对于c有list可以充当双向链表&#xff0c;unordered_map充当哈希表&#xff1b;python有OrderedDic…

Nautilus 正式发布:为 Sui 带来可验证的链下隐私计算

作为 Sui 安全工具包中的强大新成员&#xff0c;Nautilus 现已上线 Sui 测试网。它专为 Web3 开发者打造&#xff0c;支持保密且可验证的链下计算。Nautilus 应用运行于开发者自主管理的可信执行环境&#xff08;Trusted Execution Environment&#xff0c;TEE&#xff09;中&a…

云服务器CVM标准型S5实例性能测评——2025腾讯云

腾讯云服务器CVM标准型S5实例具有稳定的计算性能&#xff0c;CPU采用采用 Intel Xeon Cascade Lake 或者 Intel Xeon Cooper Lake 处理器&#xff0c;主频2.5GHz&#xff0c;睿频3.1GHz&#xff0c;CPU内存配置2核2G、2核4G、4核8G、8核16G等配置&#xff0c;公网带宽可选1M、3…

leetcode面试经典算法题——2

链接&#xff1a;https://leetcode.cn/studyplan/top-interview-150/ 20. 有效的括号 给定一个只包括 ‘(’&#xff0c;‘)’&#xff0c;‘{’&#xff0c;‘}’&#xff0c;‘[’&#xff0c;‘]’ 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#x…

Ubuntu20.04安装企业微信

建议先去企业微信官网看一下有没有linux版本&#xff0c;没有的话在按如下方式安装&#xff0c;不过现在是没有的。 方案 1、使用docker容器 2、使用deepin-wine 3、使用星火应用商店 4. 使用星火包deepin-wine 5、使用ukylin-wine 本人对docker不太熟悉&#xff0c;现…

在Ubuntu服务器上部署xinference

一、拉取镜像 docker pull xprobe/xinference:latest二、启动容器&#xff08;GPU&#xff09; docker run -d --name xinference -e XINFERENCE_MODEL_SRCmodelscope -p 9997:9997 --gpus all xprobe/xinference:latest xinference-local -H 0.0.0.0 # 启动一个新的Docker容…

异步编程——微信小程序

1. 前言 引用来自&#xff1a;微信小程序开发中的多线程处理与异步编程_微信小程序 多线程-CSDN博客 微信小程序是基于JavaScript开发的&#xff0c;与浏览器JavaScript不同&#xff0c;小程序运行在WebView内部&#xff0c;没有多线程的概念。小程序的 JavaScript 是单线程的…

STM32 四足机器人常见问题汇总

文章不介绍具体参数&#xff0c;有需求可去网上搜索。 特别声明&#xff1a;不论年龄&#xff0c;不看学历。既然你对这个领域的东西感兴趣&#xff0c;就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题&#xff1a;提出问题时&#xff0c;应说明是哪款产品&a…

Windows 下实现 PHP 多版本动态切换管理(适配 phpStudy)+ 一键切换工具源码分享

&#x1f680; Windows 下实现 PHP 多版本动态切换管理&#xff08;适配 phpStudy&#xff09; 一键切换工具源码分享 &#x1f4e6; 工具特点&#x1f9ea; 效果展示&#x1f9f1; 环境要求&#x1f9d1;‍&#x1f4bb; 源码展示&#xff1a;php_switcher.py&#x1f6e0; 打…

ReportLab 导出 PDF(图文表格)

ReportLab 导出 PDF&#xff08;文档创建&#xff09; ReportLab 导出 PDF&#xff08;页面布局&#xff09; ReportLab 导出 PDF&#xff08;图文表格) 文章目录 1. Paragraph&#xff08;段落&#xff09;2. Table&#xff08;表格&#xff09;3. VerticalBarChart&#xff0…

yolov8复现

Yolov8的复现流程主要包含环境配置、下载源码和验证环境三大步骤&#xff1a; 环境配置 查看电脑状况&#xff1a;通过任务管理器查看电脑是否有独立显卡&#xff08;NVIDIA卡&#xff09;。若有&#xff0c;后续可安装GPU版本的pytorch以加速训练&#xff1b;若没有&#xff0…

RestSharp和Newtonsoft.Json结合发送和解析http

1.下载RestSharp和Newtonsoft.Json 2编写ApiRequest和ApiResponse和调用工具类HttpRestClient 请求模型 /// <summary>/// 请求模型/// </summary>public class ApiRequest{/// <summary>/// 请求地址/api路由地址/// </summary>public string Route {…

【Pytorch之一】--torch.stack()方法详解

torch.stack方法详解 pytorch官网注释 Parameters tensors&#xff1a;张量序列&#xff0c;也就是要进行stack操作的对象们&#xff0c;可以有很多个张量。 dim&#xff1a;按照dim的方式对这些张量进行stack操作&#xff0c;也就是你要按照哪种堆叠方式对张量进行堆叠。dim的…

数据中台(大数据平台)之数据资源目录

数据资源目录是数据管理的账本&#xff0c;是数据应用的基础&#xff0c;更是是数据治理成果的体现&#xff0c;因此数据中台产品应提供数据资源目录编制、发布、资源挂载、下架的管理能力。 1.数据资源目录分类 资源目录能够支持基于业务特点创建和维护基础目录分类和特色目…

【随身WiFi】随身WiFi Debian系统优化教程

0.操作前必看 本教程基于Debian系统进行优化&#xff0c;有些操作对随身WiFi来说可能会带来负优化&#xff0c;根据需要选择。 所有操作需要在root用户环境下运行&#xff0c;否则都要加sudo 随身wifi Debian系统&#xff0c;可以去某安的随声WiFi模块自行搜索刷机 点赞&am…

JAVA Web_定义Servlet2_学生登录验证Servlet

题目 页面StudentLogin.html中有一HTML的表单代码如下&#xff1a; <form action"studentLogin" method"post">学生姓名&#xff1a;<input type"text" name"stuName" value""><br>登录密码&#xff1a;…

Unity入门笔记(缘更)

内容来源SiKi学院的Luna’s Fantasy 文章目录 一、基础知识1.准备2.基础知识1.层级(Layer)2.轴心点3.预制体(Prefab)4.刚体组件(Rigidbody)5.碰撞器组件(BoxCollider) 二、代码1.移动 一、基础知识 1.准备 Unity安装&#xff1a; https://unity.cn 2.基础知识 1.层级(Layer…