FFMPEG保存视频流数据至本地(rtsp转mp4)

将rtsp流中的h264视频流在没解码之前获取下来,并保存到本地文件mp4中的h264流中,h264->mp4
网络摄像机rtsp地址详解。

流程图:

FFMPEG保存视频流数据至本地(rtsp转mp4)_第1张图片

源码:

.h
#ifndef RTSPSTREAMMUXTASK_H
#define RTSPSTREAMMUXTASK_H

#include 
#include 
#include 
#include 
#include 

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/mathematics.h"
}


class RtspStreamMuxTask : public QMainWindow
{
    Q_OBJECT

public:
    explicit RtspStreamMuxTask(QWidget *parent = 0);
    ~RtspStreamMuxTask();
    void rtsp();
};

#endif // RTSPSTREAMMUXTASK_H

.cpp
#include "rtspstreammuxtask.h"
#include 
#include 
#include 
#include 
#include 

RtspStreamMuxTask::RtspStreamMuxTask(QWidget *parent) :
    QMainWindow(parent)
{
    rtsp();
}

RtspStreamMuxTask::~RtspStreamMuxTask()
{
}

void RtspStreamMuxTask::rtsp()
{
        AVFormatContext *inVFmtCtx=NULL,*outFmtCtx=NULL;
        int frame_index=0;//统计帧数
        int inVStreamIndex=-1,outVStreamIndex=-1;//输入输出视频流在文件中的索引位置
        const char *inVFileName = "rtsp://admin:[email protected]:554/type=0&id=1";
        const char *outFileName = "video.mp4";

        //======================输入部分============================//

        inVFmtCtx = avformat_alloc_context();//初始化内存

        //打开输入文件
        //打开一个文件并解析。可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引。
        //参数一:AVFormatContext **ps, 格式化的上下文(由avformat_alloc_context分配)的指针。
        //参数二:要打开的流的url,地址最终会存入到AVFormatContext结构体当中。
        //参数三:指定输入的封装格式。一般传NULL,由FFmpeg自行探测。
        //参数四:包含AVFormatContext和demuxer私有选项的字典。返回时,此参数将被销毁并替换为包含找不到的选项
        if(avformat_open_input(&inVFmtCtx,inVFileName,NULL,NULL)<0){
            printf("Cannot open input file.\n");
            return ;
        }

        //查找输入文件中的流
        /*avformat_find_stream_info函数*/
        //参数一:媒体文件上下文。
        //参数二:字典,一些配置选项。      /*媒体句柄*/
        if(avformat_find_stream_info(inVFmtCtx,NULL)<0){
            printf("Cannot find stream info in input file.\n");
            return ;
        }

        //查找视频流在文件中的位置
        for(size_t i=0;i<inVFmtCtx->nb_streams;i++){//nb_streams 视音频流的个数
              //streams :输入视频的AVStream []数组  codec:该流对应的AVCodecContext
            if(inVFmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){//streams 视音频流
                inVStreamIndex=(int)i;
                break;
            }
        }     

        AVCodecParameters *codecPara = inVFmtCtx->streams[inVStreamIndex]->codecpar;//输入视频流的编码参数


        printf("===============Input information========>\n");
        av_dump_format(inVFmtCtx, 0, inVFileName, 0); //输出视频信息
        printf("===============Input information========<\n");


        //=====================输出部分=========================//
        //打开输出文件并填充格式数据
        //参数一:函数调用成功之后创建的AVFormatContext结构体。
        //参数二:指定AVFormatContext中的AVOutputFormat,确定输出格式。指定为NULL,设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。。
        //参数三:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
        //参数四:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
        if(avformat_alloc_output_context2(&outFmtCtx,NULL,NULL,outFileName)<0){
            printf("Cannot alloc output file context.\n");
            return;
        }
   

        //打开输出文件并填充数据
        if(avio_open(&outFmtCtx->pb,outFileName,AVIO_FLAG_READ_WRITE)<0){
            printf("output file open failed.\n");
            return;
        }
     

        //在输出的mp4文件中创建一条视频流
        AVStream *outVStream = avformat_new_stream(outFmtCtx,NULL);//记录视频流通道数目。存储视频流通道。
        if(!outVStream){
            printf("Failed allocating output stream.\n");
            return ;
        }

        outVStream->time_base.den=25;//AVRational这个结构标识一个分数,num为分数,den为分母(时间的刻度)
        outVStream->time_base.num=1;
        outVStreamIndex=outVStream->index;
      


        //查找编码器
        //参数一:id请求的编码器的AVCodecID
        //参数二:如果找到一个编码器,则为NULL。
        //H264/H265码流后,再调用avcodec_find_decoder解码后,再写入到/MP4文件中去
        AVCodec *outCodec = avcodec_find_decoder(codecPara->codec_id);
        if(outCodec==NULL){
            printf("Cannot find any encoder.\n");
            return;
        }


        //从输入的h264编码器数据复制一份到输出文件的编码器中
        AVCodecContext *outCodecCtx=avcodec_alloc_context3(outCodec); //申请AVCodecContext空间。需要传递一个编码器,也可以不传,但不会包含编码器。
        //AVCodecParameters与AVCodecContext里的参数有很多相同的
        AVCodecParameters *outCodecPara = outFmtCtx->streams[outVStream->index]->codecpar;

        //avcodec_parameters_copy()来copyAVCodec的上下文。
        if(avcodec_parameters_copy(outCodecPara,codecPara)<0){
            printf("Cannot copy codec para.\n");
            return;
        }
     
        //基于编解码器提供的编解码参数设置编解码器上下文参数
        //参数一:要设置参数的编解码器上下文
        //参数二:媒体流的参数信息 , 包含 码率 , 宽度 , 高度 , 采样率 等参数信息
        if(avcodec_parameters_to_context(outCodecCtx,outCodecPara)<0){
            printf("Cannot alloc codec ctx from para.\n");
            return ;
        }

        //设置编码器参数(不同参数对视频编质量或大小的影响)
        /*outCodecCtx->time_base.den=25;
        outCodecCtx->time_base.num=1;*/
        outCodecCtx->bit_rate =0;//目标的码率,即采样的码率;显然,采样码率越大,视频大小越大  比特率
        outCodecCtx->time_base.num=1;//下面两行:一秒钟25帧
        outCodecCtx->time_base.den=15;
        outCodecCtx->frame_number=1;//每包一个视频帧

     
        //打开输出文件需要的编码器
        if(avcodec_open2(outCodecCtx,outCodec,NULL)<0){
            printf("Cannot open output codec.\n");
            return ;
        }

     

        printf("============Output Information=============>\n");
        av_dump_format(outFmtCtx,0,outFileName,1);//输出视频信息
        printf("============Output Information=============<\n");


        //写入文件头
        if(avformat_write_header(outFmtCtx,NULL)<0){
            printf("Cannot write header to file.\n");
            return ;
        }
      
        //===============编码部分===============//
        //AVPacket 数据结构 显示时间戳(pts)、解码时间戳(dts)、数据时长,所在媒体流的索引等
        AVPacket *pkt = av_packet_alloc();
        //存储每一个视频/音频流信息的结构体
        AVStream *inVStream = inVFmtCtx->streams[inVStreamIndex];

        //循环读取每一帧直到读完 从媒体流中读取帧填充到填充到Packet的数据缓存空间
        while(av_read_frame(inVFmtCtx,pkt)>=0){//循环读取每一帧直到读完
            pkt->dts = 0;//不加这个时间戳会出问题,时间戳比之前小的话 FFmpeg会选择丢弃视频包,现在给视频包打时间戳可以重0开始依次递增。
            if(pkt->stream_index==inVStreamIndex){//确保处理的是视频流 stream_index标识该AVPacket所属的视频/音频流。
                //FIXME:No PTS (Example: Raw H.264)
                //Simple Write PTS
                //如果当前处理帧的显示时间戳为0或者没有等等不是正常值
                if(pkt->pts==AV_NOPTS_VALUE){
                    printf("frame_index:%d\n", frame_index);

                    //Write PTS时间 刻度
                    AVRational time_base1 = inVStream->time_base;

                    //Duration between 2 frames (us) 时长
                    //AV_TIME_BASE 时间基
                    //av_q2d(AVRational);该函数负责把AVRational结构转换成double,通过这个函数可以计算出某一帧在视频中的时间位置
                    //r_frame_rate
                    int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(inVStream->r_frame_rate);
                    //Parameters参数
                    pkt->pts = (double)(frame_index*calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);
                    pkt->dts = pkt->pts;
                    pkt->duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
                    frame_index++;
                }
                //Convert PTS/DTS
                //AVPacket
                // pts 显示时间戳
                // dts 解码时间戳
                // duration 数据的时长,以所属媒体流的时间基准为单位
                // pos 数据在媒体流中的位置,未知则值为-1
                // 标识该AVPacket所属的视频/音频流。
                pkt->pts = av_rescale_q_rnd(pkt->pts, inVStream->time_base, outVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->dts = av_rescale_q_rnd(pkt->dts, inVStream->time_base, outVStream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
                pkt->duration = av_rescale_q(pkt->duration, inVStream->time_base, outVStream->time_base);
                pkt->pos = -1;
                pkt->stream_index = outVStreamIndex;
                printf("Write 1 Packet. size:%5d\tpts:%ld\n", pkt->size, pkt->pts);

                //Write
                if (av_interleaved_write_frame(outFmtCtx, pkt) < 0) {
                    printf("Error muxing packet\n");
                    break;
                }
                //处理完压缩数据之后,并且在进入下一次循环之前,
                //记得使用 av_packet_unref 来释放已经分配的AVPacket->data缓冲区。
                av_packet_unref(pkt);
            }
        }
      
        av_write_trailer(outFmtCtx);

        //=================释放所有指针=======================
        av_packet_free(&pkt);//堆栈上数据缓存空间
        av_free(inVStream);//存储每一个视频/音频流信息的结构体
        av_free(outVStream);//在输出的mp4文件中创建一条视频流
        avformat_close_input(&outFmtCtx);//关闭一个AVFormatContext
        avcodec_close(outCodecCtx);
        avcodec_free_context(&outCodecCtx);
        av_free(outCodec);
        avcodec_parameters_free(&outCodecPara);
        avcodec_parameters_free(&codecPara);
        avformat_close_input(&inVFmtCtx);//关闭一个AVFormatContext
        avformat_free_context(inVFmtCtx);//销毁函数
        avio_close(outFmtCtx->pb);

}

你可能感兴趣的:(FFmpeg,Qt,Linux,音视频,qt,ffmpeg,c++)