C++实现ffmpeg,h264编码,rtsp取流,从流中取包至自定义的缓冲区,在缓冲区中挑选所需要的连续的帧封装为mp4

大体思路如下:
将流中的packet取出统一存储至缓冲区,为了方便在缓冲区里摘取需要的连续的包,在存储时定义一个帧号,随包一块存入,为了方便查询,这里使用链表作为存储区,随后在缓冲区里依照帧号取出packet进行封装,这里要注意时间戳。

第一步:从流中拿到packet和帧号存入缓冲区,
注意:1.普通的临时变量不能存储packet,需要使用av_packet_alloc()申请一块内存,使用av_packet_ref(packet_save, packet)复制。
2.考虑到内存泄漏,向缓冲区存放智能指针
3.考虑到入流的启停,不将保存视频放入线程池,单独启一个线程分离出去

void SaveAlarmVideo::savePacket(AVPacket* packet, int packettime)
{
    
    
    int ret;
    AVPacket *packet_save;
    packet_save = av_packet_alloc();
    ret = av_packet_ref(packet_save, packet);
    std::shared_ptr<AVPacket> packet_save_ptr(packet_save,[](AVPacket *packet_save){
    
    av_packet_unref(packet_save);});

    if(ret < 0)
    {
    
    
        std::cout << "copy packet error" << std::endl;
    }
    
    CPacketInfo info(packet_save_ptr,packettime);
    m_buffer.enqueue(info);

    auto frame_rate = GetFrameRate(*m_ifmt_ctx);

    auto alarm_pair = getfromAlarmVideo();
    if(alarm_pair.first == -1)
    {
    
    
        return;
    }

    if((alarm_pair.first + frame_rate*3) <= packettime)
    {
    
    
            eraseAlarmVideo();
            std::list<CPacketInfo> alarm_list = m_buffer.getVideoDatabyTime(alarm_pair.first,frame_rate*3);
            std::thread t1(&SaveAlarmVideo::saveVideo,*((alarm_pair.second).begin()),*m_ifmt_ctx,alarm_list,alarm_pair,m_client);
            t1.detach();       
    }
}

获取当前帧率的函数:

int SaveAlarmVideo::GetFrameRate(const AVFormatContext ifmt_ctx)
{
    
    
    int frame_rate;
    AVStream *in_stream;
    
    for(int i = 0;i < ifmt_ctx.nb_streams; i++)
    {
    
    
        if(ifmt_ctx.streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
    
    
            in_stream = ifmt_ctx.streams[i];
            if(in_stream->avg_frame_rate.den != 0 && in_stream->avg_frame_rate.num != 0)
		    {
    
    
		        frame_rate = in_stream->avg_frame_rate.num/in_stream->avg_frame_rate.den;
		    }
        }
    }
    return frame_rate;
}

第二步:保存视频并发送视频

void SaveAlarmVideo::saveVideo(const std::string out_filename,const AVFormatContext ifmt_ctx,const std::list<CPacketInfo> alarm_list,const std::pair<int, std::list<std::string>> alarm_pair, std::shared_ptr<HttpClient> m_client)
{
    
    
    
    int ret,dts;
    int dts_last = 0;
    AVFormatContext *ofmt_ctx = NULL;
    AVStream *out_stream,*in_stream;
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename.data());//创建视频文件
    if (!ofmt_ctx) 
    {
    
    

        std::cout<< "Could not create output context" <<std::endl;

        ret = AVERROR_UNKNOWN;

        exit(0);

    }
    out_stream = avformat_new_stream(ofmt_ctx, NULL);//创建流

    if (!out_stream) 
    {
    
    

        std::cout<<"Failed allocating output stream"<<std::endl;

        exit(0);

    }
    

    for(int i = 0;i < ifmt_ctx.nb_streams; i++)
    {
    
    
        if(ifmt_ctx.streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//找出视频流不需音频流,出流参数按照入流参数拷贝
        {
    
    

            in_stream = ifmt_ctx.streams[i];

            ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);

            if (ret < 0) 
            {
    
    
                std::cout<<"Failed to copy codec parameters"<<std::endl;

                exit(0);

            }
            out_stream->codecpar->codec_tag = 0;

        }
    }


    av_dump_format(ofmt_ctx, 0, out_filename.data(), 1);

    if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) 
    {
    
    

        ret = avio_open(&ofmt_ctx->pb, out_filename.data(), AVIO_FLAG_WRITE);

        if (ret < 0) 
        {
    
    

            std::cout<<"Could not open output file "<<std::endl;

            exit(0);

        }

    }

    ret = avformat_write_header(ofmt_ctx, NULL);//写头
    if (ret < 0) 
    {
    
    

        std::cout<< "Error occurred when opening output file"<<std::endl;

        exit(0);

    }
    int frame_count = 0;//视频从0s开始
    for(auto it :alarm_list)
    {
    
    

            it.m_packet_ptr->duration = av_rescale_q(it.m_packet_ptr->duration,in_stream->time_base, out_stream->time_base);
            if(it.m_packet_ptr->duration > 5000 || it.m_packet_ptr->duration < 3000)
            {
    
    
                it.m_packet_ptr->duration = 3600;
            }//当入流视频不正常时,给一个靠谱的时间基
            it.m_packet_ptr->pts = frame_count * it.m_packet_ptr->duration;

            it.m_packet_ptr->dts = it.m_packet_ptr->pts;

            it.m_packet_ptr->pos = -1;

            auto packet2 = av_packet_alloc();
            av_packet_ref(packet2,it.m_packet_ptr.get());//第二个告警视频有可能用到第一个视频的帧,写帧后会释放掉原本的packet,故拷贝一份packet用于释放

            int ret = av_interleaved_write_frame(ofmt_ctx, packet2);

            if (ret < 0) 
            {
    
    
                std::cout <<"write error"<<std::endl;

                break;

            }
            
            frame_count++;

    }
        av_write_trailer(ofmt_ctx);
        if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))

        avio_closep(&ofmt_ctx->pb);

        avformat_free_context(ofmt_ctx);

        for(auto it = (alarm_pair.second).begin();it != (alarm_pair.second).end();it++ )
        {
    
    
                
                std::string video_name = *(alarm_pair.second.begin());
               
                sendAlarmVideo(m_client,*it,video_name);
                
                
        }
        remove(out_filename.data());//发送以后删除本地视频文件
        // eraseAlarmVideo();
}

告警帧统一推送至m_alarm_video保存:

void SaveAlarmVideo::eraseAlarmVideo()
{
    
    
    std::unique_lock<std::mutex> lk(m_mutex);
    m_alarm_video.erase(m_alarm_video.begin());
    
}
std::pair<int, std::list<std::string>> SaveAlarmVideo::getfromAlarmVideo()
{
    
    
    std::unique_lock<std::mutex> lk(m_mutex);
    if(m_alarm_video.empty())
    {
    
    
        return {
    
    -1,{
    
    }};
    }
    return *m_alarm_video.begin();
}
bool SaveAlarmVideo::SetTimeFilename(const std::string& out_filename,int timestamp)
{
    
    
    std::unique_lock<std::mutex> lk(m_mutex);
    m_alarm_video[timestamp].push_back(out_filename);
    return true;

}

头文件整理:

#pragma once
#include <iostream>
#include "cap_ffmpeg_legacy_api.hpp"
#include <thread>
#include <string>
#include <list>
#include <mutex>
#include <condition_variable>
#include <limits>
#include "utils/thrd_list.hpp"
#include "utils/ThreadPool.h"
#include "common.h"
#include "utils/handleApi.hpp"
#include "utils/net_util.h"
#include<cstdio>
extern "C"
{
    
    
#include <libavformat/avformat.h> 
#include <libavcodec/avcodec.h> 
#include <libavdevice/avdevice.h> 
#include <libavfilter/avfilter.h> 
#include <libavutil/avutil.h> 
#include <libswscale/swscale.h>
#include <libavutil/timestamp.h>
}
#define AV_PKT_FLAG_KEY     0x0001

class CPacketInfo
{
    
    
public:
    std::shared_ptr<AVPacket> m_packet_ptr;
    int m_timestamp;
    CPacketInfo(std::shared_ptr<AVPacket> packet_ptr,int timestamp): m_packet_ptr(packet_ptr),m_timestamp(timestamp){
    
    }
    ~CPacketInfo()
    {
    
    
        // av_packet_unref(m_packet);
    }
};

class SaveAlarmVideo
{
    
    
public:
    SaveAlarmVideo()
    {
    
    
        m_client = std::make_shared<HttpClient>();
        m_client->host = g_opt.send_video_host;
        m_client->port = g_opt.send_video_port;
        m_client->config.timeout = 60;
        m_client->config.timeout_connect = 60;
        m_buffer.setSize(300);
        m_buffer.setFullModel(true);
    } 
    void savePacket(AVPacket* packet, int packettime);
    bool isSave(int timestamp);
    bool setInCtx(AVFormatContext *ifmt_ctx);
    bool SetTimeFilename(const std::string& out_filename,int timestamp);
    void static saveVideo(const std::string out_filename,const AVFormatContext ifmt_ctx,const std::list<CPacketInfo> alarm_list,const std::pair<int, std::list<std::string>> alarm_pair,std::shared_ptr<HttpClient> m_client);
    std::pair<int, std::list<std::string>> getfromAlarmVideo();
    void eraseAlarmVideo();
    int GetFrameRate(const AVFormatContext ifmt_ctx);
    

private:
    AVStream *m_in_stream;
    AVFormatContext *m_ifmt_ctx = NULL;

    ThrdList<CPacketInfo> m_buffer;
    std::mutex m_mutex;
    
    std::map<int, std::list<std::string>> m_alarm_video;
    std::shared_ptr<HttpClient> m_client;
    
    
};

猜你喜欢

转载自blog.csdn.net/m0_43407388/article/details/120272472