基于ffmpeg实现多路rtsp拉流解码为yuv420p

news/2024/10/15 12:25:38/

一:前言

        FFmpeg 是一个非常强大的多媒体框架,它可以用来处理视频和音频数据。它包括了命令行工具 ffmpegffplayffprobe 等,以及一套可以用来开发多媒体应用的库(libavcodec、libavformat、libavutil、libswscale 等)。FFmpeg 支持多种音视频格式的转换、编码、解码、流处理等操作。

        RTSP(Real Time Streaming Protocol)是一种网络控制协议,用于建立和控制媒体流的会话。它常用于流媒体应用,比如视频监控系统。RTSP 允许客户端控制媒体流,如播放、暂停、快进等,并且可以支持多种流媒体传输协议,如 RTP(Real-time Transport Protocol)。

二:功能分析

        功能要求:支持多路流媒体信号同时进行解码和保存也就是录播功能。

        功能分析:要想实现多路流媒体信号同时进行解码功能的实现,首先会想到一个问题,那就是同步性,对于多路并非运行而言,其多路从事的其实是一个工作,那就是通过avformat_open_input()打开对应的流文件,然后再对这个流文件进行处理。

        功能实现:更具功能分析可以得到一个实现方案,那就是使用线程并发性的特点,利用多线程互不干扰同时进行的特点来实现多路的流媒体并发解码。下面的示例为两路流并发运行的例子,多路流同样的道理有多少路采用多少线程。思路清晰,条理清晰,直接上具体实现功能。

三:功能实现

单路拉流解码流程

1:打开输入流文件

使用ffmpeg的接口函数:

avformat_alloc_context()

创建一个存储多媒体容器格式信息的结构体。然后用:avformat_open_input()打开流文件。

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options);

其中的第一个参数就是上面函数的返回值,第二个参数就是流地址的RUL,第三个参数为NULL,第四个参数的作用为设置一些解复用器特定的选项,rtsp的默认传输是UDP,可以通过这个参数加上av_dict_set()函数来修改为 TCP协议。

2:寻找媒体信息

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

第一个参数为上面的创建结构体的函数返回值,第二个为NULL。

然后访问里面的流媒体信息。

fctx->nb_streams;//有几个流媒体

fctx->streams[0];//一般为视频流媒体

fctx->streams[1];//音频

3:创建上下文

avcodec_alloc_context3(NULL)
int avcodec_parameters_to_context(AVCodecContext *codec, const AVCodecParameters *params);

拷贝流媒体中的数据。

fctx->streams[video_index]->codecpar

4:寻找解码器

AVCodec *avcodec_find_decoder(enum AVCodecID id);

通过ID来寻找对应的解码器。

5:绑定上下文

打开编码器;avcodec_open2();

int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

6:编码并保存

接口函数:

int av_read_frame(AVFormatContext *s, AVPacket *pkt);
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *pkt);
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

分别的功能为:

向输入流中读取一个数据包

向解码器发送一个压缩数据包

从解码器中接收解码后的帧。

基本解码功能就结束了,要注意资源的回收和释放

av_frame_unref();

释放 AVFrame 结构体中的所有引用的资源

av_packet_unref();

用于释放 AVPacket 结构体中的所有资源。

两路拉流功能实现

两路是用俩线程分别实现俩路解码的过程,解码流程大体不变,不一样的点就是加入两个线程。具体代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavdevice/avdevice.h>
#include <pthread.h>// 定义视频流索引
int video_index1 = -1;
int video_index2 = -1;
AVFormatContext *fctx1 = NULL;
AVFormatContext *fctx2 = NULL;
// 线程处理函数
void *decode_stream(void *arg) 
{AVFormatContext *format_ctx = (AVFormatContext *)arg;AVCodecContext *codec_ctx = NULL;AVCodec *codec = NULL;AVPacket packet;AVFrame *frame = av_frame_alloc();int ret;FILE *file = NULL;// 寻找视频流int video_stream_index = video_index1;if (format_ctx == fctx2) {video_stream_index = video_index2;file = fopen("2.yuv", "w+");} else {file = fopen("1.yuv", "w+");}if (!file) {printf("创建YUV文件失败\n");return NULL;}// 获取解码器上下文codec_ctx = avcodec_alloc_context3(NULL);avcodec_parameters_to_context(codec_ctx, format_ctx->streams[video_stream_index]->codecpar);// 寻找解码器codec = avcodec_find_decoder(codec_ctx->codec_id);if (codec == NULL) {printf("未找到解码器\n");fclose(file);return NULL;}// 打开解码器if (avcodec_open2(codec_ctx, codec, NULL) < 0) {printf("无法打开解码器\n");fclose(file);return NULL;}// 读取数据并解码av_init_packet(&packet);packet.data = NULL;packet.size = 0;while (av_read_frame(format_ctx, &packet) >= 0) {if (packet.stream_index == video_stream_index) {ret = avcodec_send_packet(codec_ctx, &packet);if (ret < 0) {printf("发送数据到解码器失败\n");break;}while (ret >= 0) {ret = avcodec_receive_frame(codec_ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {break;} else if (ret < 0){printf("解码失败\n");break;}// 写入YUV数据fwrite(frame->data[0], 1, frame->linesize[0] * frame->height, file);fwrite(frame->data[1], 1, frame->linesize[1] * frame->height / 2, file);fwrite(frame->data[2], 1, frame->linesize[2] * frame->height / 2, file);av_frame_unref(frame);}av_packet_unref(&packet);}}// 释放资源if (frame) {av_frame_free(&frame);}if (codec_ctx) {avcodec_free_context(&codec_ctx);}if (file) {fclose(file);}return NULL;
}int main() 
{avformat_network_init();AVDictionary *options1 = NULL;AVDictionary *options2 = NULL;//定义两路流媒体URLchar filepath1[] = "rtsp://192.168.xx.xx/xx/xx";//根据自己的URL设置char filepath2[] = "rtsp://xxx.xxx.xx.xx.";av_dict_set(&options1, "buffer_size", "1080000", 0);av_dict_set(&options1, "rtsp_transport", "tcp", 0);av_dict_set(&options1, "stimeout", "5000000", 0);av_dict_set(&options1, "max_delay", "500000", 0);av_dict_set(&options2, "buffer_size", "720000", 0);av_dict_set(&options2, "rtsp_transport", "tcp", 0);av_dict_set(&options2, "stimeout", "5000000", 0);av_dict_set(&options2, "max_delay", "500000", 0);//1:打开输入流文件fctx1 = avformat_alloc_context();fctx2 = avformat_alloc_context();if (avformat_open_input(&fctx1, filepath1, NULL, &options1) < 0) {printf("第一路流文件打开失败\n");return -1;}if (avformat_open_input(&fctx2, filepath2, NULL, &options2) < 0) {printf("第二路流文件打开失败\n");avformat_close_input(&fctx1);avformat_free_context(fctx1);return -1;}if (avformat_find_stream_info(fctx1, NULL) < 0) {printf("第一路流文件非流媒体文件\n");avformat_close_input(&fctx1);avformat_close_input(&fctx2);avformat_free_context(fctx1);avformat_free_context(fctx2);return -1;}if (avformat_find_stream_info(fctx2, NULL) < 0) {printf("第二路流文件非流媒体文件\n");avformat_close_input(&fctx1);avformat_close_input(&fctx2);avformat_free_context(fctx1);avformat_free_context(fctx2);return -1;}//寻找对应的视频流索引for (int i = 0; i < fctx1->nb_streams; i++) {if (fctx1->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_index1 = i;break;}}for (int i = 0; i < fctx2->nb_streams; i++) {if (fctx2->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {video_index2 = i;break;}}//创建线程pthread_t tid1, tid2;pthread_create(&tid1, NULL, decode_stream, fctx1);pthread_create(&tid2, NULL, decode_stream, fctx2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);avformat_close_input(&fctx1);avformat_close_input(&fctx2);avformat_free_context(fctx1);avformat_free_context(fctx2);return 0;
}


http://www.ppmy.cn/news/1539433.html

相关文章

LangChain使用Prompt02

1.设置提示 from langchain.prompts import ChatPromptTemplate prompt_template ChatPromptTemplate.from_messages([("system", "你是一位专业的翻译&#xff0c;能够将{input_language}翻译成{output_language}&#xff0c;并且输出文本会根据用户要求的任…

云原生周刊:优化 Uber 的持续部署丨2024.10.14

开源项目推荐 Cog Cog 是将机器学习模型打包到容器的工具。可通过配置将机器学习模型所需的环境和依赖&#xff0c;自动打包到容器里方便部署&#xff0c;让你不再为编写 Docker 文件和 CUDA 而痛苦&#xff0c;还能自动启动 HTTP 接口服务方便调用。 KnowStreaming KnowSt…

基于FreeRTOS的LWIP移植

目录 前言一、移植准备工作二、以太网固件库与驱动2.1 固件库文件添加2.2 库文件修改2.3 添加网卡驱动 三、LWIP 数据包和网络接口管理3.1 添加LWIP源文件3.2 Lwip文件修改3.2.1 修改cc.h3.2.2 修改lwipopts.h3.2.3 修改icmp.c3.2.4 修改sys_arch.h和sys_arch.c3.2.5 修改ether…

架构设计笔记-18-安全架构设计理论与实践

知识要点 常见的安全威胁&#xff1a; 信息泄露&#xff1a;信息被泄露或透露给某个非授权的实体。破坏信息的完整性&#xff1a;数据被非授权地进行增删、修改或破坏而受到损失。拒绝服务&#xff1a;对信息或其他资源的合法访问被无条件地阻止。攻击者向服务器发送大量垃圾…

【浏览器】如何正确使用Microsoft Edge

1、清理主页广告 如今的Microsoft Edge 浏览器 主页太乱了&#xff0c;各种广告推送&#xff0c;点右上角⚙️设置&#xff0c;把快速链接、网站导航、信息提要、背景等全部关闭。这样你就能得到一个超级清爽的主页。 网站导航       关闭 …

mysql--数据表的操作

说明&#xff1a;filed为列名 关于数据表的操作 目录 1、创建表 2、查看表结构 &#xff08;1&#xff09;显示表结构 &#xff08;2&#xff09;显示具体表信息 3、修改表结构 &#xff08;1&#xff09;删除表 &#xff08;2&#xff09;修改表名 &#xff08;3&am…

Dockerr安装Oracle以及使用DBeaver连接

拉取镜像 pull container-registry.oracle.com/database/free:latest 创建容器 说明一下我现在的最新版本是23 docker run -d --name oracle23i -h xrilang -p 1521:1521 container-registry.oracle.com/database/free:latest 查看日志 docker logs oracle23i 设置密码 因为创建…

python爬虫,爬取网页壁纸图片

python爬虫实战&#xff0c;爬取网页壁纸图片 使用python爬取壁纸图片&#xff0c;保存到本地。 爬取彼岸图网&#xff0c;网站地址https://pic.netbian.com/ 本人小白&#xff0c;记录一下学习过程。 开始前的准备 安装python环境&#xff0c;略。 python编辑器pycharm2…