大体思路如下:
将流中的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;
};