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

devtools/2025/2/21 15:59:35/

文章目录

  • 一、背景
  • 二、解析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));}// 释放 bufferav_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;@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic void onClick(View view) {decodeLib.stop();}});}private void startRtsp(String url){decodeLib.addEventCallback(new DecodeLib.EventCallback() {@Overridepublic void onReceiveData(byte[] yuv) {// todo 这里显示图片runOnUiThread(new Runnable() {@Overridepublic 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.ppmy.cn/devtools/160719.html

相关文章

nginx ngx_http_module(10) 指令详解

nginx ngx_http_module(10) 指令详解 相关链接 nginx 嵌入式变量解析目录nginx 嵌入式变量全目录nginx 指令模块目录nginx 指令全目录 一、目录 1.1 模块简介 ngx_http_v2_module&#xff1a;HTTP/2支持模块&#xff0c;允许Nginx通过HTTP/2协议与客户端进行通信。HTTP/2带来…

VScode内接入deepseek包过程(本地部署版包会)

目录 1. 首先得有vscode软件 2. 在我们的电脑本地已经部署了ollama&#xff0c;我将以qwen作为实验例子 3. 在vscode上的扩展商店下载continue 4. 下载完成后&#xff0c;依次点击添加模型 5. 在这里可以添加&#xff0c;各种各样的模型&#xff0c;选择我们的ollama 6. 选…

PLC通信交互系统技术分享

目录 0、前言 1、模块划分 2、状态机 3、通信层增强 4、异常处理机制 5、核心代码 关键状态处理示例 6、部署与测试方案 1. 环境要求 2. 性能测试指标 0、前言 这是一个C程序&#xff0c;用于与西门子PLC进行通信&#xff0c;处理SN码、拍照信号、检测结果等流程。代码…

DeepSeek 新注意力架构NSA

DeepSeek 新注意力架构NSA概要 研究背景&#xff1a; 实现高效长上下文建模的自然方法是利用 softmax 注意力的固有稀疏性&#xff0c;通过选择性计算关键 query-key 对&#xff0c;可以显著减少计算开销&#xff0c;同时保持性能。最近这一路线的进展包括多种策略&#xff1…

接入DeepSeek后,智慧园区安全调度系统的全面提升

随着人工智能技术的快速发展&#xff0c;智慧园区的安全管理正逐步向智能化、自动化方向迈进。DeepSeek作为先进的人工智能解决方案&#xff0c;为智慧园区安全调度系统注入了强大的技术动力。通过接入DeepSeek&#xff0c;智慧园区安全调度系统在多个方面实现了显著提升&#…

请谈谈 Vue 中的响应式原理,如何实现?

一、Vue2响应式原理&#xff1a;Object.defineProperty的利与弊 实现原理&#xff1a; // 数据劫持核心实现 function defineReactive(obj, key, val) {const dep new Dep(); // 依赖收集容器Object.defineProperty(obj, key, {get() {if (Dep.target) { // 当前Watcher实例…

高性能内存对象缓存Memcached详细实验操作

目录 前提准备&#xff1a; cache1&#xff0c;2&#xff1a; 客户端cache-api&#xff08;一定得是LAMP环境&#xff09; memcache实现主主复制以及高可用(基于以上完成) cache1,2: memcachekeepalived(基于以上完成) cache1,2: 前提准备&#xff1a; 1. 准备三台cent…

Docker部署CRMEB多店版再优化

原部署方案在容器中包含了nginx&#xff0c;这个是不必须的&#xff0c;可以拿掉。现优化如下&#xff1a; Dockerfile内容&#xff1a; 去掉了nginx内容。 # 使用官方的Ubuntu 24.04镜像作为基础镜像 FROM ubuntu:24.04# 设置环境变量以避免交互式配置工具 ENV DEBIAN_FRON…