#include "VideoCodec.h"
#include <stdio.h>
#include <iostream>
#include <qeventloop.h>
#include <QTime>
#include <QCoreApplication>
#include <QCryptographicHash>
using namespace std;
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
//-------zty 封装为mp4-------
AVFormatContext* fmt_ctx_visual = nullptr;
AVCodecContext* enc_ctx_visual = nullptr;
AVStream* stream_visual = nullptr;
int64_t pts_counter_visual = 0;
AVFormatContext* fmt_ctx_infrared = nullptr;
AVCodecContext* enc_ctx_infrared = nullptr;
AVStream* stream_infrared = nullptr;
int64_t pts_counter_infrared = 0;
AVFormatContext* fmt_ctx_depth = nullptr;
AVCodecContext* enc_ctx_depth = nullptr;
AVStream* stream_depth = nullptr;
int64_t pts_counter_depth = 0;
bool SucOfVisualStore = true;
bool SucOfInfraredStore = true;
bool SucOfDepthStore = true;
void init_mp4_writer(const char* filename1, const char* filename2,const char* filename3,int index,int width, int height) {
switch (index) {
case 1:
{
if(SucOfVisualStore)
{
// 创建输出上下文
avformat_alloc_output_context2(&fmt_ctx_visual, nullptr, "mp4", filename1);
// 查找编码器(H.264)
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
enc_ctx_visual = avcodec_alloc_context3(codec);
// 配置编码器参数
enc_ctx_visual->bit_rate = 400000;
enc_ctx_visual->width = width;
enc_ctx_visual->height = height;
enc_ctx_visual->time_base = (AVRational){1, 30}; // 帧率25fps
enc_ctx_visual->framerate = (AVRational){30, 1};
enc_ctx_visual->gop_size = 10;
enc_ctx_visual->max_b_frames = 1;
enc_ctx_visual->pix_fmt = AV_PIX_FMT_YUV420P;
// 打开编码器
avcodec_open2(enc_ctx_visual, codec, nullptr);
// 创建视频流
stream_visual = avformat_new_stream(fmt_ctx_visual, nullptr);
avcodec_parameters_from_context(stream_visual->codecpar, enc_ctx_visual);
// 打开输出文件
avio_open(&fmt_ctx_visual->pb, filename1, AVIO_FLAG_WRITE);
avformat_write_header(fmt_ctx_visual, nullptr);
SucOfVisualStore = false;
}
}
break;
case 2:
{
if(SucOfInfraredStore)
{
// 创建输出上下文
avformat_alloc_output_context2(&fmt_ctx_visual, nullptr, "mp4", filename2);
// 查找编码器(H.264)
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
enc_ctx_infrared = avcodec_alloc_context3(codec);
// 配置编码器参数
enc_ctx_infrared->bit_rate = 400000;
enc_ctx_infrared->width = width;
enc_ctx_infrared->height = height;
enc_ctx_infrared->time_base = (AVRational){1, 30}; // 帧率25fps
enc_ctx_infrared->framerate = (AVRational){30, 1};
enc_ctx_infrared->gop_size = 10;
enc_ctx_infrared->max_b_frames = 1;
enc_ctx_infrared->pix_fmt = AV_PIX_FMT_YUV420P;
// 打开编码器
avcodec_open2(enc_ctx_infrared, codec, nullptr);
// 创建视频流
stream_infrared = avformat_new_stream(fmt_ctx_infrared, nullptr);
avcodec_parameters_from_context(stream_infrared->codecpar, enc_ctx_infrared);
// 打开输出文件
avio_open(&fmt_ctx_infrared->pb, filename2, AVIO_FLAG_WRITE);
avformat_write_header(fmt_ctx_infrared, nullptr);
SucOfInfraredStore = false;
}
}
break;
case 3:
{
if(SucOfDepthStore)
{
// 创建输出上下文
avformat_alloc_output_context2(&fmt_ctx_depth, nullptr, "mp4", filename3);
// 查找编码器(H.264)
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_H264);
enc_ctx_depth = avcodec_alloc_context3(codec);
// 配置编码器参数
enc_ctx_depth->bit_rate = 400000;
enc_ctx_depth->width = width;
enc_ctx_depth->height = height;
enc_ctx_depth->time_base = (AVRational){1, 30}; // 帧率25fps
enc_ctx_depth->framerate = (AVRational){30, 1};
enc_ctx_depth->gop_size = 10;
enc_ctx_depth->max_b_frames = 1;
enc_ctx_depth->pix_fmt = AV_PIX_FMT_YUV420P;
// 打开编码器
avcodec_open2(enc_ctx_depth, codec, nullptr);
// 创建视频流
stream_depth = avformat_new_stream(fmt_ctx_depth, nullptr);
avcodec_parameters_from_context(stream_depth->codecpar, enc_ctx_depth);
// 打开输出文件
avio_open(&fmt_ctx_depth->pb, filename3, AVIO_FLAG_WRITE);
avformat_write_header(fmt_ctx_depth, nullptr);
SucOfDepthStore = false;
}
}
break;
default:
break;
}
}
void saveFrameToMP4(AVFrame* frame,int index) {
switch (index) {
case 1:
{
// 设置帧时间戳
frame->pts = pts_counter_visual++;
// 发送帧到编码器
avcodec_send_frame(enc_ctx_visual, frame);
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_visual, pkt) == 0) {
// 设置包时间戳
av_packet_rescale_ts(pkt, enc_ctx_visual->time_base, stream_visual->time_base);
pkt->stream_index = stream_visual->index;
// 写入文件
av_interleaved_write_frame(fmt_ctx_visual, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
}
break;
case 2:
{
// 设置帧时间戳
frame->pts = pts_counter_infrared++;
// 发送帧到编码器
avcodec_send_frame(enc_ctx_infrared, frame);
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_infrared, pkt) == 0) {
// 设置包时间戳
av_packet_rescale_ts(pkt, enc_ctx_infrared->time_base, stream_infrared->time_base);
pkt->stream_index = stream_infrared->index;
// 写入文件
av_interleaved_write_frame(fmt_ctx_infrared, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
}
break;
case 3:
{
// 设置帧时间戳
frame->pts = pts_counter_depth++;
// 发送帧到编码器
avcodec_send_frame(enc_ctx_depth, frame);
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_depth, pkt) == 0) {
// 设置包时间戳
av_packet_rescale_ts(pkt, enc_ctx_depth->time_base, stream_depth->time_base);
pkt->stream_index = stream_depth->index;
// 写入文件
av_interleaved_write_frame(fmt_ctx_depth, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
}
break;
default:
break;
}
}
void finalize_mp4_writer(int index) {
switch (index) {
case 1:
{
if (enc_ctx_visual || enc_ctx_visual->codec) {
// 刷新编码器缓冲区
avcodec_send_frame(enc_ctx_visual, nullptr); // 发送空帧刷新
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_visual, pkt) == 0) {
av_packet_rescale_ts(pkt, enc_ctx_visual->time_base, stream_visual->time_base);
pkt->stream_index = stream_visual->index;
av_interleaved_write_frame(fmt_ctx_visual, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
// 写入文件尾
av_write_trailer(fmt_ctx_visual);
// 释放资源
avcodec_free_context(&enc_ctx_visual);
avio_closep(&fmt_ctx_visual->pb);
avformat_free_context(fmt_ctx_visual);
}
}
break;
case 2:
{
// 刷新编码器缓冲区
avcodec_send_frame(enc_ctx_infrared, nullptr); // 发送空帧刷新
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_infrared, pkt) == 0) {
av_packet_rescale_ts(pkt, enc_ctx_infrared->time_base, stream_infrared->time_base);
pkt->stream_index = stream_infrared->index;
av_interleaved_write_frame(fmt_ctx_infrared, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
// 写入文件尾
av_write_trailer(fmt_ctx_infrared);
// 释放资源
avcodec_free_context(&enc_ctx_infrared);
avio_closep(&fmt_ctx_infrared->pb);
avformat_free_context(fmt_ctx_infrared);
}
break;
case 3:
{
// 刷新编码器缓冲区
avcodec_send_frame(enc_ctx_depth, nullptr); // 发送空帧刷新
AVPacket* pkt = av_packet_alloc();
while (avcodec_receive_packet(enc_ctx_depth, pkt) == 0) {
av_packet_rescale_ts(pkt, enc_ctx_depth->time_base, stream_depth->time_base);
pkt->stream_index = stream_depth->index;
av_interleaved_write_frame(fmt_ctx_depth, pkt);
av_packet_unref(pkt);
}
av_packet_free(&pkt);
// 写入文件尾
av_write_trailer(fmt_ctx_depth);
// 释放资源
avcodec_free_context(&enc_ctx_depth);
avio_closep(&fmt_ctx_depth->pb);
avformat_free_context(fmt_ctx_depth);
}
break;
default:
break;
}
}
void VideoCodec::run()
{
int videoIndex = -1;
// 初始化环境
if (!Init(videoIndex))
{
return;
}
// 分配包空间
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
// 逐帧解析数据
while (av_read_frame(m_pFormatCtx, packet) == 0) {
//如果是视频数据
if (packet->stream_index == videoIndex) {
//解码一帧视频数据
int got_picture = 0;
int ret = avcodec_decode_video2(m_pCodecCtx, m_pFrame, &got_picture, packet);
if (ret < 0) {
printf("Decode Error.\n");
return;
}
if (got_picture) {
sws_scale(m_pImgConvertCtx, (const unsigned char* const*)m_pFrame->data, m_pFrame->linesize, 0, m_pCodecCtx->height,
m_pFrameRGB->data, m_pFrameRGB->linesize);
QImage img((uchar*)m_pFrameRGB->data[0], m_pCodecCtx->width, m_pCodecCtx->height, QImage::Format_RGB32);
// QImage image = img.copy();
//img=img.scaled(275,230);//zty 把这句话注释了,放在了主线程
emit sig_GetOneFrame(img,VideoStyle);//zty 加了一个emit
if(storeornot)
{
QByteArray latin1Data_visual = path_visual.toLatin1();
QByteArray latin1Data_infrared = path_infrared.toLatin1();
QByteArray latin1Data_depth = path_depth.toLatin1();
init_mp4_writer(latin1Data_visual.constData(),latin1Data_infrared.constData(), latin1Data_depth.constData(),VideoStyle,1920, 1080);
AVFrame* frame = m_pFrame;
saveFrameToMP4(frame,VideoStyle);
}
Delay(10);
}
}
// 释放包空间
av_free_packet(packet);
}
// 清理数据
CleanUp();
}
bool VideoCodec::Init(int& videoIndex)
{
unsigned char* out_buffer;
avformat_network_init();
AVDictionary* avdic = NULL;
QByteArray ba = UdpORtcp.toLatin1();
char* tcpudp = ba.data();
av_dict_set(&avdic, "rtsp_transport", tcpudp, 0);
// av_dict_set(&avdic, "stimeout", "3000000", 0);原生的ffmpeg参数在打开RTSP流时,若连接不上,会出现卡死在打开函数的情况,在有些情况下这是很不好的,可以通过设置超时来改变卡死的情况
av_dict_set(&avdic, "max_delay", "10", 0);//rtsp情况下的最大延时
// 缓冲区大小16K
av_dict_set(&avdic, "buffer_size", "16777216", 0); //原生的ffmpeg参数在对1920x1080的RTSP流进行播放时,花屏现象很严重,根据网上查的资料,可以通过增大“buffer_size”参数来提高画质,减少花屏现象,当你rtsp流码率很大的时候,设置“buffer_size”很有必要,否则内存泄漏,并出现花屏。
av_dict_set(&avdic, "reorder_queue_size", "5000", 0);
// rtsp流网址
//std::string strUrl = "rtsp://172.16.0.240:8554/live/stream";//Url.toStdString();//lcj
std::string strUrl = Url.toStdString();//lcj
const char* urlpath = strUrl.c_str();
std::string strFilepath = m_FilePath.toStdString();
const char* filepath = strFilepath.c_str();
// 初始化编解码库
av_register_all();//创建AVFormatContext对象,与码流相关的结构。
m_pFormatCtx = avformat_alloc_context();
// 初始化pFormatCtx结构打开视频文件,从视频流中解析出部分信息,填充到pFormatCtx中,包含视频的分辨率,时间戳等信息,而且包含了相应的解码器信息
if (/*isurlpath_filepath*/true)
{
if (avformat_open_input(&m_pFormatCtx, urlpath, NULL, &avdic) != 0)
{
printf("Couldn't open input stream.\n");
return false;
}
}
else
{
if (avformat_open_input(&m_pFormatCtx, filepath, NULL, NULL) != 0)
{
printf("Couldn't open the file.\n");
return false;
}
}
// 获取音视频流数据信息
if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0)
{
printf("Couldn't find stream information.\n");
return false;
}
// nb_streams视音频流的个数,这里当查找到视频流时就中断了。
for (int i = 0; i < m_pFormatCtx->nb_streams; i++)
{
if (m_pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoIndex = i;
break;
}
}
// 判断是否找到视频流
if (videoIndex == -1)
{
printf("Didn't find a video stream.\n");
return false;
}
// 获取视频流编码结构
m_pCodecCtx = m_pFormatCtx->streams[videoIndex]->codec;
// 查找解码器
AVCodec* pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);
if (pCodec == NULL)
{
printf("Codec not found.\n");
return false;
}
// 用于初始化pCodecCtx结构
if (avcodec_open2(m_pCodecCtx, pCodec, NULL) < 0)
{
printf("Could not open codec.\n");
return false;
}
// 创建帧结构,此函数仅分配基本结构空间,图像数据空间需通过av_malloc分配
m_pFrame = av_frame_alloc();
m_pFrameRGB = av_frame_alloc();
// 创建动态内存,创建存储图像数据的空间
// av_image_get_buffer_size获取一帧图像需要的大小
out_buffer = (unsigned char*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_RGB32, m_pCodecCtx->width, m_pCodecCtx->height, 1));
av_image_fill_arrays(m_pFrameRGB->data, m_pFrameRGB->linesize, out_buffer,
AV_PIX_FMT_RGB32, m_pCodecCtx->width, m_pCodecCtx->height, 1);
// 此函数打印输入或输出的详细信息,非视频文件会报错
// av_dump_format(pFormatCtx, 0, filepath, 0);
// 初始化img_convert_ctx结构
m_pImgConvertCtx = sws_getContext(m_pCodecCtx->width, m_pCodecCtx->height, m_pCodecCtx->pix_fmt,
m_pCodecCtx->width, m_pCodecCtx->height, AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
// 标识当前环境处于运行状态,未被清理过
m_bCleaned = false;
return true;
}
void VideoCodec::CleanUp()
{
if (m_bCleaned)
{
return;
}
// 清理环境
sws_freeContext(m_pImgConvertCtx);
av_frame_free(&m_pFrameRGB);
av_frame_free(&m_pFrame);
avcodec_close(m_pCodecCtx);
avformat_close_input(&m_pFormatCtx);
// 标识视频已播放完毕或已停止,环境已被清理
m_bCleaned = true;
}
void VideoCodec::slot_exeisover()
{
finalize_mp4_writer(VideoStyle);
}
在这段代码中,当关闭程序时在析构函数调用slot_exeisover函数,但是avcodec_send_frame(enc_ctx_visual, nullptr); 这句代码报错导致存储的mp4文件无法打开