基于FFmpeg接收RTSP的ts流

 

RTSP用于建立的控制媒体流的传输,通过wireshark抓包可以看到rtsp消息交互的过程:

1. 第一步:查询服务器端可用方法

C->S:OPTION request     // 询问S有哪些方法可用

S->C:OPTION response    // S回应信息的public头字段中包括提供的所有可用方法过程

 

2. 第二步:得到媒体描述信息

C->S:DESCRIBE request   // 要求得到S提供的媒体描述信息

S->C:DESCRIBE response  // S回应媒体描述信息,一般是sdp信息

 

3. 第三步:建立RTSP会话

C->S:SETUP request    // 通过Transport头字段列出可接受的传输选项,请求S建立会话

S->C:SETUP response   // S建立会话,通过Transport头字段返回选择的具体转输选项,并返回建立的Session ID

 

4. 第四步:请求开始传送数据

C->S:PLAY request    // C请求S开始发送数据

S->C:PLAY response   // S回应该请求的信息

 

5. 第五步: 数据传送播放中

S->C:发送流媒体数据  // 通过RTP协议传送数据

 

6. 第六步:关闭会话,退出

C->S:TEARDOWN request // C请求关闭会话

S->C:TEARDOWN response // S回应该请求

 

基于FFmpeg接收RTSP传输的ts流并保存实现步骤:

组件和网络初始化——>打开网络流——>获取网络流信息——>根据网络流信息初始化输出流信息——>创建并打开ts文件——>写ts文件头——>循环读取输入流并写入ts文件——>写文件尾——>关闭流,关闭文件

 

关键函数解析:

int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options); //打开网络流或文件流

int avformat_write_header(AVFormatContext *s, AVDictionary **options);//根据文件名的后缀写相应格式的文件头

int av_read_frame(AVFormatContext *s, AVPacket *pkt);//从输入流中读取一个分包

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);//往输出流中写一个分包

int av_write_trailer(AVFormatContext *s);//写输出流(文件)的文件尾

 

数据结构:AVFormatContext,AVStream,AVCodecContext,AVPacket,AVFrame等,一个AVFormatContext包含多个AVStream,每个码流包含了AVCodec和AVCodecContext,AVPicture是AVFrame的一个子集,他们都是数据流在编解过程中用来保存数据缓存的对像,从数据流读出的数据首先是保存在AVPacket里,也可以理解为一个AVPacket最多只包含一个AVFrame,而一个AVFrame可能包含好几个AVPacket,AVPacket是种数据流分包的概念。

源码

#include <stdio.h>

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"

};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
#endif

int main(int argc, char **argv)
{
	AVOutputFormat *ofmt = NULL;
	AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
	AVPacket pkt;
	const char *in_filename, *out_filename;
	int ret, i;
	int video_index = -1;
	int frame_index = 0;
	in_filename = "rtsp://192.168.1.103:8554/1";
	out_filename = "receive.h264";

	av_register_all();
	avformat_network_init(); 
    //打开输入流	
    if((ret=avformat_open_input(&ifmt_ctx,in_filename,0,0))<0)
    {
        printf("Could not open input file.\n");
		system("pause");
        goto end;
    }
	
    if((ret=avformat_find_stream_info(ifmt_ctx,0))<0)
    {
        printf("Failed to retrieve input stream information\n");
        goto end;
    }
	
    //nb_streams代表有几路流,一般是2路:即音频和视频,顺序不一定
    for(i=0;i<ifmt_ctx->nb_streams;i++){
        
        if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            //这一路是视频流,标记一下,以后取视频流都从ifmt_ctx->streams[video_index]取
            video_index=i;
            break;
        }
    }
	
    av_dump_format(ifmt_ctx,0,in_filename,0);

    //打开输出流
    avformat_alloc_output_context2(&ofmt_ctx,NULL,NULL,out_filename);
    
    if(!ofmt_ctx)
    {
        printf("Could not create output context\n");
        ret=AVERROR_UNKNOWN;
        goto end;
    }
	
    ofmt = ofmt_ctx->oformat;
    for(i=0;i<ifmt_ctx->nb_streams;i++)
    {    //根据输入流创建输出流
        AVStream *in_stream = ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(ofmt_ctx,in_stream->codec->codec);
        if(!out_stream)
        {
            printf("Failed allocating output stream.\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        //将输出流的编码信息复制到输入流
        ret = avcodec_copy_context(out_stream->codec,in_stream->codec);
        if(ret<0)
        {
            printf("Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        out_stream->codec->codec_tag = 0;
    
        if(ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;

    }
	
    //Dump format--------------------
    av_dump_format(ofmt_ctx,0,out_filename,1);
    //打开输出文件
    if(!(ofmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&ofmt_ctx->pb,out_filename,AVIO_FLAG_WRITE);
        if(ret<0)
        {
            printf("Could not open output URL '%s'",out_filename);
            goto end;
        }
    }
	
    //写文件头到输出文件
    ret = avformat_write_header(ofmt_ctx,NULL);
    if(ret < 0)
    {
        printf("Error occured when opening output URL\n");
        goto end;
    }
	
    //while循环中持续获取数据包,不管音频视频都存入文件
    while(1)
    {
        AVStream *in_stream,*out_stream;
        //从输入流获取一个数据包
        ret = av_read_frame(ifmt_ctx,&pkt);
        if(ret<0)
            break;

        in_stream = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
        //copy packet
        //转换 PTS/DTS 时序
        pkt.pts = av_rescale_q_rnd(pkt.pts,in_stream->time_base,out_stream->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));  
        //printf("pts %d dts %d base %d\n",pkt.pts,pkt.dts, in_stream->time_base);
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base); 
        pkt.pos = -1;  

		printf("Write 1 Packet. size:%5d\tpts:%lld\n", pkt.size, pkt.pts);
        //此while循环中并非所有packet都是视频帧,当收到视频帧时记录一下,仅此而已
        if(pkt.stream_index==video_index)
        {
            printf("Receive %8d video frames from input URL\n",frame_index);
            frame_index++;
        }

        //将包数据写入到文件。
        ret = av_interleaved_write_frame(ofmt_ctx,&pkt);
        if(ret < 0)
        {
            /*
            当网络有问题时,容易出现到达包的先后不一致,pts时序混乱会导致
            av_interleaved_write_frame函数报 -22 错误。暂时先丢弃这些迟来的帧吧
            若所大部分包都没有pts时序,那就要看情况自己补上时序(比如较前一帧时序+1)再写入。
            */
            if(ret==-22){
                continue;
            }else{
                printf("Error muxing packet.error code %d\n" , ret);
                break;
            }       
        }
        
        //av_free_packet(&pkt); //此句在新版本中已deprecated 由av_packet_unref代替
        av_packet_unref(&pkt);
		
    }

    //写文件尾
    av_write_trailer(ofmt_ctx);

end:  
    avformat_close_input(&ifmt_ctx);
    //Close input
    if(ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    if(ret<0 && ret != AVERROR_EOF)
    {
        printf("Error occured.\n");
        return -1;
    }

    return 0;
   
}

 

运行结果

 

Linux下编译:

gcc main.cpp -g -o main.out -I /usr/local/ffmpeg/include -L /usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil

VLC搭建RTSP服务器的过程

https://blog.csdn.net/beitiandijun/article/details/9232405

 

 

 

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值