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

一:前言

        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;
    //定义两路流媒体URL
    char 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;
}

ffmpeg 是一个强大的音视频处理工具,它可以用来进行各种音视频格式的编解码、转码、剪辑等操作。下面是基于 C++ 使用 ffmpeg 进行 RTSP 拉流和推流的流程: 1. 引入 ffmpeg 库:首先需要在项目中引入 ffmpeg 库,可以使用静态库或者动态库,具体方法不再赘述。 2. 初始化 ffmpeg:在使用 ffmpeg 前,需要初始化 ffmpeg,这可以通过调用 av_register_all() 函数实现。 3. 创建 AVFormatContext:创建一个 AVFormatContext 对象,用于存储音视频流的相关信息,包括音视频编码格式、流的时间基等信息。可以通过调用 avformat_alloc_context() 函数来创建。 4. 打开 RTSP 流:调用 avformat_open_input() 函数打开 RTSP 流,传入 RTSP 地址、AVFormatContext 对象等参数,函数会自动解析出音视频流的信息并存储到 AVFormatContext 对象中。 5. 查找音视频流:通过调用 avformat_find_stream_info() 函数,可以查找音视频流的索引,该函数会自动解析音视频流的信息,并将音视频流的索引存储到 AVFormatContext 对象中。 6. 获取音视频流的信息:可以通过遍历 AVFormatContext 对象的 streams 属性,获取每个音视频流的详细信息,包括编码格式、分辨率、码率等等。 7. 打开音视频解码器:对于每个音视频流,需要打开相应的解码器,可以通过调用 avcodec_find_decoder() 函数查找对应的解码器,然后调用 avcodec_open2() 函数打开解码器。 8. 创建 AVFrame 和 AVPacket:解码音视频帧需要使用 AVFrame 和 AVPacket 对象,可以通过调用 av_frame_alloc() 和 av_packet_alloc() 函数创建。 9. 读取音视频帧:通过调用 av_read_frame() 函数读取音视频帧,该函数会返回一个 AVPacket 对象,包含了音视频帧的数据和相关信息。 10. 解码音视频帧:根据 AVPacket 对象中的信息,可以调用对应的解码器进行解码解码后的结果存储在 AVFrame 对象中。 11. 处理音视频帧:可以对解码后的音视频帧进行各种处理,比如转换格式、合并音视频等。 12. 推流:可以使用 avformat_new_stream() 函数创建一个新的音视频流,并设置相应的参数,然后使用 avio_open() 函数打开一个输出流,最后调用 avformat_write_header() 函数开始推流。 13. 写入音视频帧:对于每一帧音视频数据,可以调用 av_interleaved_write_frame() 函数写入输出流中,该函数会自动进行封装和编码。 14. 关闭流和解码器:最后记得关闭输入流、输出流和解码器,释放相应的资源。 以上是基于 C++ 使用 ffmpeg 进行 RTSP 拉流和推流的大致流程,具体实现还需要根据具体的需求和情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值