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

news2025/7/29 16:33:36

文章目录

  • 一、背景
  • 二、解析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

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

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

相关文章

Jmeter进阶篇(34)如何解决jmeter.save.saveservice.timestamp_format=ms报错?

问题描述 今天使用Jmeter完成压测执行,然后使用命令将jtl文件转换成html报告时,遇到了报错! 大致就是说jmeter里定义了一个jmeter.save.saveservice.timestamp_format=ms的时间格式,但是jtl文件中的时间格式不是标准的这个ms格式,导致无法正常解析。对于这个问题,有如下…

Navicat17详细安装教程(附最新版本安装包和补丁)2025最详细图文教程安装手册

目录 前言&#xff1a;为什么选择Navicat 17&#xff1f; 一、下载Navicat17安装包 二、安装Navicat 1.运行安装程序 2.启动安装 3.同意“协议” 4.设置安装位置 5.创建桌面图标 6.开始安装 7.安装完成 三、安装补丁 1.解押补丁包 2.在解压后的补丁包目录下找到“w…

一文详解U盘启动Legacy/UEFI方式以及GPT/MBR关系

对于装系统的老手而说一直想研究一下装系统的原理&#xff0c;以及面对一些问题时的解决思路&#xff0c;故对以前的方法进行原理上的解释&#xff0c;主要想理解其底层原理。 引导模式 MBR分区可以同时支持UEFI和Legacy引导&#xff0c;我们可以看一下微pe制作的启动盘&#…

LeetCode 热题 100_搜索二维矩阵(64_74_中等_C++)(二分查找)(暴力破解法;Z字形查找;一次二分查找)

LeetCode 热题 100_搜索二维矩阵&#xff08;64_74&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;暴力破解法&#xff09;&#xff1a;思路二&#xff08;Z字形查找&#xff09;&#xff1a;思路三&#x…

学习量化交易的环境安装记录

1、安装anaconda 因为使用python&#xff0c;需要安装anaconda&#xff0c;具体是下面的官方地址&#xff0c;根据自己需要下载相应的版本 https://www.anaconda.com/download 运行上面下载的文件&#xff0c;安装anaconda 可以根据自己需要安装到相应的盘上面 同时环境变量…

MySQL数据库入门到大蛇尚硅谷宋红康老师笔记 高级篇 part 1

第01章_Linux下MySQL的安装与使用 首先在vmware中下载centos7&#xff0c;实际上8更好一点&#xff0c;不过centos已经是时代的眼泪了&#xff0c;我之前已经教过了&#xff0c;不过是忘了&#xff0c;所以重新说一遍&#xff0c;看文档即可 2.开机前修改mac地址 &#xff0…

基于AVue的二次封装:快速构建后台管理系统的CRUD方案

基于AVue的二次封装&#xff1a;快速构建后台管理系统的CRUD方案 在开发后台管理系统时&#xff0c;表格是常见的组件之一。然而&#xff0c;使用原生的Element Plus实现CRUD&#xff08;增删改查&#xff09;功能往往需要编写大量重复代码&#xff0c;过程繁琐。即使借助类似…

第6章:基于LangChain如何开发Agents,附带客户支持智能体示例

本文主要介绍了 LangChain4j 中的 Agent&#xff08;代理&#xff09; 概念&#xff0c;以及如何使用 LangChain4j 构建代理系统&#xff0c;重点提供了一个客户支持系统的智能体样例 代理&#xff08;Agents&#xff09;| LangChain4j 注意&#xff1a; 请注意&#xff0c;“A…

传统的自动化行业的触摸屏和上位机,PLC是否会被取代?

传统的自动化行业的触摸屏和上位机是否会被取代&#xff1f; 在工业自动化领域&#xff0c;触摸屏和上位机长期扮演着核心角色&#xff0c;尤其在污水处理、化工生产等场景中&#xff0c;它们通过实时数据采集、逻辑控制、报警联动等功能&#xff0c;保障了生产设备的稳定运行…

智能合约的部署

https://blog.csdn.net/qq_40261606/article/details/123249473 编译 点击图中的 “Compile 1_Storage.sol” 存和取一个数的合约&#xff0c;remix自带 pragma solidity >0.8.2 <0.9.0; /*** title Storage* dev Store & retrieve value in a variable* custom:d…

word$deepseep

1、进入官网地址。 DeepSeek 2、进入DeepSeek的API文档 3、点击DeepSeek开放平台左侧的“API Keys”, 再点击“创建API Key” 4、在弹出的对话框中&#xff0c;输入自己的API Key名称&#xff0c;点击创建。 sk-0385cad5e19346a0a4ac8b7f0d7be428 5、打开Word文档。 6、Word找…

Mac系统下使用Docker快速部署MaxKB:打造本地知识库问答系统

随着大语言模型的广泛应用&#xff0c;知识库问答系统逐渐成为提升工作效率和个人学习的有力工具。MaxKB是一款基于LLM&#xff08;Large Language Model&#xff09;大语言模型的知识库问答系统&#xff0c;支持多模型对接、文档上传和自动爬取等功能。本文将详细介绍如何在Ma…

如何为自己的 PDF 文件添加密码?在线加密 PDF 文件其实更简单

随着信息泄露和数据安全问题的日益突出&#xff0c;保护敏感信息变得尤为重要。加密 PDF 文件是一种有效的手段&#xff0c;可以确保只有授权用户才能访问或修改文档内容。本文将详细介绍如何使用 CleverPDF 在线工具为你的 PDF 文件添加密码保护&#xff0c;确保其安全性。 为…

华为昇腾910b服务器部署DeepSeek翻车现场

最近到祸一台HUAWEI Kunpeng 920 5250&#xff0c;先看看配置。之前是部署的讯飞大模型&#xff0c;发现资源利用率太低了。把5台减少到3台&#xff0c;就出了他 硬件配置信息 基本硬件信息 按照惯例先来看看配置。一共3块盘&#xff0c;500G的系统盘&#xff0c; 2块3T固态…

hive—常用的函数整理

1、size(split(...))函数用于计算分割后字符串数组的长度 实例1&#xff09;&#xff1a;由客户编号列表计算客户编号个数 --数据准备 with tmp_test01 as ( select tag074445270 tag_id,202501busi_mon , 012399931003,012399931000 index_val union all select tag07444527…

Unity Mirror 多房间匹配

文章目录 一 、一些唠叨二 、案例位置三、多房间匹配代码解析四、关于MatchInterestManagement五、总结 一 、一些唠叨 最近使用Mirror开发了一款多人同时在线的肉鸽塔防游戏,其目的是巩固一下Mirror这个插件的熟练度,另一方面是想和身边的朋友一起玩一下自己开发的游戏. 但是…

基于flask+vue框架的的医院预约挂号系统i1616(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。

系统程序文件列表 项目功能:用户,医生,科室信息,就诊信息,医院概况,挂号信息,诊断信息,取消挂号 开题报告内容 基于FlaskVue框架的医院预约挂号系统开题报告 一、研究背景与意义 随着医疗技术的不断进步和人们健康意识的日益增强&#xff0c;医院就诊量逐年增加。传统的现场…

Rust编程语言入门教程(五)猜数游戏:生成、比较神秘数字并进行多次猜测

Rust 系列 &#x1f380;Rust编程语言入门教程&#xff08;一&#xff09;安装Rust&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;二&#xff09;hello_world&#x1f6aa; &#x1f380;Rust编程语言入门教程&#xff08;三&#xff09; Hello Cargo&#x1f…

【代码审计】-Tenda AC 18 v15.03.05.05 /goform接口文档漏洞挖掘

路由器&#xff1a;Tenda AC 18 v15.03.05.05 固件下载地址&#xff1a;https://www.tenda.com.cn/material?keywordac18 1./goform/SetSpeedWan 接口文档&#xff1a; formSetSpeedWan函数中speed_di参数缓冲区溢出漏洞&#xff1a; 使用 binwalk -eM 解包固件&#xff0c…

2025年02月21日Github流行趋势

项目名称&#xff1a;source-sdk-2013 项目地址url&#xff1a;https://github.com/ValveSoftware/source-sdk-2013项目语言&#xff1a;C历史star数&#xff1a;7343今日star数&#xff1a;929项目维护者&#xff1a;JoeLudwig, jorgenpt, narendraumate, sortie, alanedwarde…