android调用ffmpeg解析rtsp协议的视频流

文章目录

  • 一、背景
  • 二、解析rtsp数据
    • 1、C层功能代码
    • 2、jni层的定义
    • 3、app层的调用
  • 三、源码下载

一、背景

本demo主要介绍android调用ffmpeg中的接口解析rtsp协议的视频流(不解析音频),得到yuv数据,把yuv转bitmap在android设备上显示,涉及到打开视频、解封装、解码、回调yuv数据。学习记录帖,C语言小白,不足的地方请指正,多谢!

二、解析rtsp数据

1、C层功能代码

Decoder.h


#ifndef DECODERTSP_DECODER_H
#define DECODERTSP_DECODER_H
#include <thread>
#include "include/jniLog.h"
extern "C"
{
#include <libavutil/time.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/packet.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/opt.h>
#include <functional>
};

using namespace std;
// 定义一个回调函数类型(typedef 用于为已有的数据类型创建一个别名)
typedef std::function<void(uint8_t *buf, int size)> Callback;

// 解析rtsp视频流
int ReadFrameAndDecoder(const char *url,Callback callback);
void DoStop();
#endif //DECODERTSP_DECODER_H

Decoder.cpp


#include "include/Decoder.h"
#include "include/jniLog.h"
bool isStop = false;
int ReadFrameAndDecoder(const char* m_Url,Callback callback){
    char url[100] = {0};
    strcpy(url,m_Url);
    AVFormatContext *pFormatCtx = avformat_alloc_context();

    AVDictionary *options = NULL;
    av_dict_set(&options, "buffer_size", "1024000", 0);// 设置缓冲区大小
    av_dict_set(&options, "stimeout", "20000000", 0);
    av_dict_set(&options, "max_delay", "30000000", 0);
//    av_dict_set(&options, "rtsp_transport", "tcp", 0); //使用 TCP 传输

    LOGI("ReadFrameAndDecoder:url = %s",url);
    if (avformat_open_input(&pFormatCtx, url, NULL, NULL) != 0)     // 打开视频文件
        return -1;

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0)    // 查找视频流
        return -2;

    //视频解码,需要找到视频对应的AVStream所在pFormatCtx->streams的索引位置
    int video_stream_idx = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++) {
        //根据类型判断,是否是视频流
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_idx = i;
            break;
        }
    }

    //只有知道视频的编码方式,才能够根据编码方式去找到解码器
    //获取视频流中的编解码上下文
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(NULL);;
    avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[video_stream_idx]->codecpar);
    // AVDISCARD_NONKEY;  // 丢弃非关键帧(如B帧)
    // AVDISCARD_NONINTRA;  // 丢弃所有非帧内编码的帧(这个参数不会灰屏)
    // AVDISCARD_NONREF     // 丢弃所有非参考帧
    // AVDISCARD_BIDIR      // 丢弃所有双向预测帧
    //  AVDISCARD_DEFAULT
//    pCodecCtx->skip_frame = AVDISCARD_NONKEY;

    //4.根据编解码上下文中的编码id查找对应的解码
    //AVCodec *pCodec = const_cast<AVCodec *>(avcodec_find_decoder(pCodecCtx->codec_id));
    AVCodec *pCodec = const_cast<AVCodec *>(avcodec_find_decoder(AV_CODEC_ID_HEVC));
    if (pCodec == NULL)
        return -3;

    // 打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
        return -4;

    // 设置参数(不缓冲,低延时)
//    av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);
//    pCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;

    // 分配视频帧(装载解码后的数据)
    AVFrame *pFrame = av_frame_alloc();
    AVPacket packet; // 读取视频帧(解码前的数据包)
    while (av_read_frame(pFormatCtx, &packet) >= 0 && !isStop) {
        if (packet.stream_index == video_stream_idx) {
            int got_picture;
            // 向解码器输入数据包
            got_picture = avcodec_send_packet(pCodecCtx, &packet);
            if (got_picture < 0) {
                LOGI("got_picture = %d", got_picture);
                continue;
            }
            while (got_picture == 0) {
                // 从解码器获取帧  返回值 -11:数据包不足?
                got_picture = avcodec_receive_frame(pCodecCtx, pFrame);
                //此时,pFrame中的数据就是YUV数据
                if(got_picture == 0){
                    // 计算 frame 的数据大小
                    int data_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pFrame->width, pFrame->height, 1);
                    if (data_size < 0) {
                        LOGE("Could not calculate buffer size");
                        return -5;
                    }

                    // 分配内存来存储 byte[]
                    uint8_t *buffer = (uint8_t *)av_malloc(data_size * sizeof(uint8_t));
                    if (!buffer) {
                        LOGE("Could not allocate memory for buffer");
                        return -6;
                    }

                    // 将 AVFrame 的数据复制到 buffer 中
                    int ret = av_image_copy_to_buffer(buffer, data_size,
                                                      (const uint8_t * const *)pFrame->data, pFrame->linesize,
                                                      AV_PIX_FMT_YUV420P, pFrame->width, pFrame->height, 1);
                    if (ret < 0) {
                        LOGE("Could not copy image data to buffer");
                        av_free(buffer);
                        return -7;
                    }

                    if (callback){
                        callback(buffer,data_size * sizeof(uint8_t));
                    }
                    // 释放 buffer
                    av_free(buffer);
                }else{
                    LOGI("avcodec_receive_frame # got_picture = %d", got_picture);
                }
            }
        }
        av_packet_unref(&packet);
    }

    // 释放资源
    av_frame_free(&pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    return 0;
}

void DoStop(){
    isStop = true;
}

2、jni层的定义

native-lib.cpp

#include <jni.h>
#include <string>
#include <include/jniLog.h>
#include "include/Decoder.h"

extern "C"
JNIEXPORT jint JNICALL
Java_com_hisign_decodertsp_DecodeLib_native_1readAndDecode(JNIEnv *env, jobject thiz,
                                                           jstring rtsp_url) {
    // TODO: implement native_readAndDecode()
    const char *url = env->GetStringUTFChars(rtsp_url, nullptr);
    LOGI("url = %s", url);
    // java层的回调函数
    jmethodID mid = env->GetMethodID(env->GetObjectClass(thiz), "packetEventCallback", "([B)V");

    if(!mid){
        LOGI("StartRestAndDecodePackage, mid is null");
    }
    // 获取java层的回调函数
    Callback dataCallback = [&env, &mid, &thiz](uint8_t *buf, int size){
        // todo 通过jni调用java层返回数据
        if(mid != nullptr){
            jbyteArray  array1 = env->NewByteArray(size);
            env->SetByteArrayRegion(array1,0,size,(jbyte*)buf);
            env->CallVoidMethod(thiz, mid, array1);
            env->DeleteLocalRef(array1);
        }
    };
    int ret = ReadFrameAndDecoder(url,dataCallback);
    LOGI("ReadFrameAndDecoder ret = %d",ret);
    env->ReleaseStringUTFChars(rtsp_url, url);
    return 0;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_hisign_decodertsp_DecodeLib_native_1Stop(JNIEnv *env, jobject thiz) {

    // TODO: implement native_Stop()
    DoStop();
    return 0;
}

3、app层的调用


public class DecodeLib {
    static {
        System.loadLibrary("decodertsp");
    }

    private EventCallback mEventCallback = null;

    // 开始解码
    public void start(String url) {
        native_readAndDecode(url);
    }

    public void stop() {
        native_Stop();
    }

    public void addEventCallback(EventCallback callback) {
        mEventCallback = callback;
    }
    // 被native层回调
    private void packetEventCallback(byte[] data) {
        if (mEventCallback != null)
            mEventCallback.onReceiveData(data);
    }

    // 测试读取视频帧并解码
    private native int native_readAndDecode(String rtsp_url);

    private native int native_Stop();

    /**
     * 返回yuv数据
     */
    public interface EventCallback {
        void onReceiveData(byte[] yuv);
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private final static String KEY_IP  = "KEY_IP";
    private static int width = 1920;
    private static int height = 1080;
    private ActivityMainBinding binding;
    private DecodeLib decodeLib;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        decodeLib = new DecodeLib();
        String ip = SPUtils.getInstance().getString(KEY_IP);
        binding.etIp.setText(ip);
        binding.btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String ip = binding.etIp.getText().toString();
                if(TextUtils.isEmpty(ip)){
                    Toast.makeText(MainActivity.this, "please input ip!", Toast.LENGTH_SHORT).show();
                    return;
                }
                SPUtils.getInstance().put(KEY_IP,ip);
                String url = "rtsp://"+ip+":554/livestream/0";
                new Thread(){
                    public void run(){
                        startRtsp(url);
                    }
                }.start();
                KeyboardUtils.hideSoftInput(MainActivity.this);
            }
        });

        binding.btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                decodeLib.stop();
            }
        });
    }

    private void startRtsp(String url){
        decodeLib.addEventCallback(new DecodeLib.EventCallback() {
            @Override
            public void onReceiveData(byte[] yuv) {
                // todo 这里显示图片
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap bmp = YuvToBitmapConverter.i420ToBitmap(yuv, width, height);
                        if(bmp != null){
                            binding.img.setImageBitmap(bmp);
                        }
                    }
                });
            }
        });
        decodeLib.start(url);
    }
}

三、源码下载

https://download.csdn.net/download/dami_lixm/90408495?spm=1001.2014.3001.5503

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/975855.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

内网网络安全的解决之道

本文简要分析了企业内部网络所面临的主要分析&#xff0c;阐述了安全管理人员针对不同威胁的主要技术应对措施。进一步介绍了业界各种技术措施的现状&#xff0c;并提出了未来可能的发展趋势。 内网网络安全问题的提出 网络安全对于绝大多数人而言指的都是互联网安全&#xff…

【Redis原理】底层数据结构 五种数据类型

文章目录 动态字符串SDS(simple dynamic string )SDS结构定义SDS动态扩容 IntSetIntSet 结构定义IntSet的升级 DictDict结构定义Dict的扩容Dict的收缩Dict 的rehash ZipListZipListEntryencoding 编码字符串整数 ZipList的连锁更新问题 QuickListQuickList源码 SkipListRedisOb…

Orange 单体架构 - 快速启动

1 后端服务 1.1 基础设施 组件说明版本MySQLMySQL数据库服务5.7/8JavaJava17redis-stackRedis向量数据库最新版本Node安装Node22.11.0 1.2 orange-dependencies-parent 项目Maven依赖版本管理 1.2.1 项目克隆 GitHub git clone https://github.com/hengzq/orange-depende…

在线骑行|基于SpringBoot的在线骑行网站设计与实现(源码+数据库+文档)

在线骑行网站系统 目录 基于SpringBoot的在线骑行设计与实现 一、前言 二、系统设计 三、系统功能设计 5.1用户信息管理 5.2 路线攻略管理 5.3路线类型管理 5.4新闻赛事管理 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取…

内外网文件传输 安全、可控、便捷的跨网数据传输方案

一、背景与痛点 在内外网隔离的企业网络环境中&#xff0c;员工与外部协作伙伴&#xff08;如钉钉用户&#xff09;的文件传输面临以下挑战&#xff1a; 安全性风险&#xff1a;内外网直连可能导致病毒传播、数据泄露。 操作繁琐&#xff1a;传统方式需频繁切换网络环境&…

Unity学习笔记-Unity了解,安装,简单配置(一)

Unity 是什么&#xff1f; Unity 是一款广受欢迎的跨平台游戏开发引擎&#xff0c;由 Unity Technologies 公司开发并推出。它以强大的功能和易用性&#xff0c;在游戏开发领域占据着举足轻重的地位&#xff0c;甚至可以说&#xff0c;它改变了游戏开发的格局。凭借其出色的跨…

骁勇善战的量化利器:多因子模型【量化理论】

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲alpha策略制定后的测试问题 风险模型雏形 股票因子受多种因素影响&#xff0c;其价格由多种因素决定&#xff0c;所谓的多因子策略就是要发掘诸如此类的因子&#xff0c;以一种合理的方…

【DeepSeek】本地部署,保姆级教程

deepseek网站链接传送门&#xff1a;DeepSeek 在这里主要介绍DeepSeek的两种部署方法&#xff0c;一种是调用API&#xff0c;一种是本地部署。 一、API调用 1.进入网址Cherry Studio - 全能的AI助手选择立即下载 2.安装时位置建议放在其他盘&#xff0c;不要放c盘 3.进入软件后…

国产编辑器EverEdit - 文本编辑器的关键特性:文件变更实时监视,多头编辑不掉坑

1 监视文件变更 1.1 应用场景 某些时候&#xff0c;用户会使用多个编辑器打开同一个文件&#xff0c;如果在A编辑器修改保存&#xff0c;但是B编辑器没有重新打开&#xff0c;直接在B编辑器修改再保存&#xff0c;则可能造成在A编辑器中修改的内容丢失&#xff0c;因此&#x…

【Linux】【网络】不同子网下的客户端和服务器通信

【Linux】【网络】不同子网下的客户端和服务器通信 前两天在进行socket()网络编程并进行测试时&#xff0c;发现在不同wifi下两个电脑无法进行连接&#xff0c;大概去查找了如何解决 看到可以使用 frp 这个快速反向代理实现。 frp 可让您将位于 NAT 或防火墙后面的本地服务器…

基于Python+django+mysql旅游数据爬虫采集可视化分析推荐系统

2024旅游推荐系统爬虫可视化&#xff08;协同过滤算法&#xff09; 基于Pythondjangomysql旅游数据爬虫采集可视化分析推荐系统 有文档说明 部署文档 视频讲解 ✅️基于用户的协同过滤推荐算法 卖价就是标价~ 项目技术栈 Python语言、Django框架、MySQL数据库、requests网络爬虫…

基于 go-wrk 在 Windows 环境下对 Go Web 应用进行 HTTP 压力测试

基于 go-wrk 在 Windows 环境下对 Go Web 应用进行 HTTP 压力测试 这部分内容参考并搬运自 q1mi 老师的技术博客&#xff0c;原文的链接为&#xff1a;https://liwenzhou.com/posts/Go/benchmark-tools/。 压测相关术语 响应时间&#xff08;RT&#xff09;&#xff1a;指系…

CSS 媒体查询:从入门到精通,打造跨设备完美体验

在当今移动互联网时代&#xff0c;用户访问网站的设备早已不再局限于桌面电脑&#xff0c;手机、平板等各种屏幕尺寸的设备层出不穷。为了确保用户在不同设备上都能获得良好的浏览体验&#xff0c;响应式网页设计应运而生。而 CSS 媒体查询&#xff0c;正是实现响应式设计的核心…

如何在 macOS 上配置 MySQL 环境变量

如何在 macOS 上配置 MySQL 环境变量 步骤 1: 查找 MySQL 安装路径 打开终端&#xff0c;使用以下命令查找 mysql 的可执行文件路径&#xff1a; which mysql如果该命令没有返回结果&#xff0c;可以使用 find 命令&#xff1a; sudo find / -name "mysql" 2>/de…

Gin从入门到精通 (五)数据绑定与验证

数据绑定与验证 数据绑定是指将请求数据&#xff08;如 JSON、表单、URL 参数等&#xff09;绑定到 Go 语言中的结构体。Gin 提供了便捷的方法将请求中的数据映射到预定义的结构体字段上&#xff0c;使得开发者可以像访问结构体字段一样访问请求数据。 数据验证是对绑定到结构…

计算机毕业设计SpringBoot+Vue.jst网上超市系统(源码+LW文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

【论文解读】《Training Large Language Models to Reason in a Continuous Latent Space》

论文链接 1. 背景与动机 语言空间与推理的矛盾 目前大多数大语言模型&#xff08;LLMs&#xff09;在解决复杂问题时采用链式思维&#xff08;Chain-of-Thought, CoT&#xff09;方法&#xff0c;即利用自然语言逐步推导出答案。然而&#xff0c;论文指出&#xff1a; 自然语言…

DevEco Studio常用快捷键以及如何跟AndroidStudio的保持同步

DevEco Studio快捷键 DevEco Studio是华为推出的用于开发HarmonyOS应用的集成开发环境&#xff0c;它提供了丰富的快捷键以提高开发效率&#xff0c;以下为你详细介绍不同操作场景下的常用快捷键&#xff1a; 通用操作快捷键 操作描述Windows/Linux 快捷键Mac 快捷键打开设置窗…

4. MySQL 逻辑架构说明

4. MySQL 逻辑架构说明 文章目录 4. MySQL 逻辑架构说明1. 逻辑架构剖析1.1 服务器处理客户端请求1.2 Connectors(连接器)1.3 第1层&#xff1a;连接层1.4 第2层&#xff1a;服务层1.5 第3层&#xff1a;引擎层1.6 存储层 2. SQL执行流程2.1 MySQL 中的 SQL 执行流程 2.2 MySQL…

基于 Python Django 的校园互助平台(附源码,文档)

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…