You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
PayloadAPP/Src/Video/cffmpeg_decode.cpp

324 lines
9.8 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "cffmpeg_decode.h"
// Cffmpeg_decode::Cffmpeg_decode() {
Cffmpeg_decode::Cffmpeg_decode(QObject *parent) : QObject(parent) {
inputFormatCtx = avformat_alloc_context();
inputPacket = av_packet_alloc();
yuvFrame = av_frame_alloc();
rgbFrame = av_frame_alloc();
avformat_network_init();
}
Cffmpeg_decode::~Cffmpeg_decode() {
if (!inputPacket)
av_packet_free(&inputPacket);
if (!yuvFrame)
av_frame_free(&yuvFrame);
if (!rgbFrame)
av_frame_free(&rgbFrame);
if (!encoderCtx)
avcodec_free_context(&encoderCtx);
if (!encoderCtx)
avcodec_close(encoderCtx);
if (!decoderCtx)
avcodec_free_context(&decoderCtx);
if (!decoderCtx)
avcodec_close(decoderCtx);
if (!inputFormatCtx)
avformat_close_input(&inputFormatCtx);
}
void Cffmpeg_decode::setUrl(QString url) { _url = url; }
bool Cffmpeg_decode::open_input_file() {
if (_url.isEmpty())
return 0;
//========================解码============================
AVDictionary *avdic = NULL;
// 设置缓存大小1080p可将值调大
av_dict_set(&avdic, "buffer_size", "2048000", 0);
// 以udp方式打开如果以tcp方式打开将udp替换为tcp
av_dict_set(&avdic, "rtsp_transport", "tcp", 0);
// 设置超时断开连接时间,单位微秒//listen_timeout
// av_dict_set(&avdic, "listen_timeout", "200000", 0);
av_dict_set(&avdic, "stimeout", "200000", 0);
av_dict_set(&avdic, "max_delay", "3", 0); // 设置最大时延
av_dict_set(&avdic, "tune", "zerolatency", 0);
av_dict_set(&avdic, "preset", "ultrafast", 0);
inputFormatCtx->flags |= AVFMT_FLAG_NONBLOCK;
// 打开输入流
if (avformat_open_input(&inputFormatCtx, _url.toUtf8().data(), NULL, &avdic) <
0) {
printf("Cannot open input file.\n");
return 0;
}
// 查找流信息
if (avformat_find_stream_info(inputFormatCtx, NULL) < 0) {
printf("Cannot find any stream in file.\n");
return 0;
}
// 从输入流中找到第一个视频流
for (int i = 0; i < inputFormatCtx->nb_streams; i++) {
if (inputFormatCtx->streams[i]->codecpar->codec_type ==
AVMEDIA_TYPE_VIDEO) {
videoStreamIndex = i;
continue;
}
}
if (videoStreamIndex == -1) {
printf("Cannot find video stream in file.\n");
return 0;
}
// 获取视频流的解码器参数
videoCodecPara = inputFormatCtx->streams[videoStreamIndex]->codecpar;
decoder = avcodec_find_decoder(videoCodecPara->codec_id);
if (!decoder) {
printf("Cannot find valid decode codec.\n");
return 0;
}
// 为解码器上下文分配空间
decoderCtx = avcodec_alloc_context3(decoder);
if (!decoderCtx) {
printf("Cannot find valid decode codec context.\n");
return 0;
}
// 初始化解码器上下文
if (avcodec_parameters_to_context(decoderCtx, videoCodecPara) < 0) {
printf("Cannot initialize parameters.\n");
return 0;
}
// 打开解码器
if (avcodec_open2(decoderCtx, decoder, NULL) < 0) {
printf("Cannot open codec.\n");
return 0;
}
// 初始化图像转换器
swsCtx = sws_getContext(decoderCtx->width, //
decoderCtx->height, //
decoderCtx->pix_fmt, //
decoderCtx->width, //
decoderCtx->height, //
AV_PIX_FMT_RGB32, //
SWS_BICUBIC, NULL, NULL, NULL);
numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, //
decoderCtx->width, //
decoderCtx->height, //
1);
out_buffer = (unsigned char *)av_malloc(numBytes * sizeof(unsigned char));
int res = av_image_fill_arrays(rgbFrame->data, //
rgbFrame->linesize, //
out_buffer, //
AV_PIX_FMT_RGB32, //
decoderCtx->width, //
decoderCtx->height, 1);
if (res < 0) {
qDebug() << "Fill arrays failed.\n";
return 0;
}
// 推流初始化
if (bPushStreamFlag) {
emit sendInitPushStream_Signal(inputFormatCtx);
}
// 裸流保存
if (!openSave()) {
return false;
};
return true;
}
// 线程里持续执行
void Cffmpeg_decode::run() {
if (!open_input_file()) {
qDebug() << "Please open video file first.";
emit sendConnectFail(1);
IsstopPlay = true;
return;
}
if (bRecordTime) {
startTime = av_gettime();
qDebug() << "*******StartTime:" << QString::number(startTime);
bRecordTime = false;
}
// 读取数据包
while (av_read_frame(inputFormatCtx, inputPacket) >= 0) {
if (IsstopPlay) {
qDebug() << "video play stop";
break;
}
if (inputPacket->stream_index == videoStreamIndex) {
// 推流
if (bPushStreamFlag) {
// av_packet_clone(inputPacket);
AVPacket *outputPacket = av_packet_clone(inputPacket);
emit sendStreamData_Signal(outputPacket, frm_cnt);
}
// 保存裸流
if (m_formatContextSave) {
// 由于保存的m_formatContextSave只创建了一个视频流而读取到的图像的流索引不一定为0可能会出现错误【Invalid
// packet stream index: 1】
// 所以这里需要将stream_index指定为和m_formatContextSave中视频流索引相同因为就一个流所以直接设置为0
inputPacket->stream_index = 0;
av_write_frame(m_formatContextSave,
inputPacket); // 将数据包写入输出媒体文件
}
// 解码数据包
if (avcodec_send_packet(decoderCtx, inputPacket) >= 0) {
int ret;
while ((ret = avcodec_receive_frame(decoderCtx, yuvFrame)) >= 0) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return;
} else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
exit(1);
}
sws_scale(swsCtx, //
yuvFrame->data, //
yuvFrame->linesize, //
0, //
decoderCtx->height, //
rgbFrame->data, //
rgbFrame->linesize);
QImage img(out_buffer, //
decoderCtx->width, //
decoderCtx->height, //
QImage::Format_RGB32);
emit sendQImage(img);
QThread::msleep(28);
}
}
av_packet_unref(inputPacket);
frm_cnt++;
}
if (IsstopPlay) {
if (m_formatContextSave) {
saveDone();
}
break;
}
// 花屏
// if (av_read_frame(inputFormatCtx, inputPacket) < 0) {
// break; // 达到文件末尾
// }
}
if (m_formatContextSave) {
saveDone();
}
qDebug() << "All video play done";
}
// 退出
void Cffmpeg_decode::stop() {
// 写入输出文件的尾信息
// av_write_trailer(outputFormatCtx);
IsstopPlay = true;
if (!inputPacket)
av_packet_free(&inputPacket);
if (!yuvFrame)
av_frame_free(&yuvFrame);
if (!rgbFrame)
av_frame_free(&rgbFrame);
if (!encoderCtx)
avcodec_free_context(&encoderCtx);
if (!encoderCtx)
avcodec_close(encoderCtx);
if (!decoderCtx)
avcodec_free_context(&decoderCtx);
if (!decoderCtx)
avcodec_close(decoderCtx);
if (!inputFormatCtx)
avformat_close_input(&inputFormatCtx);
}
void Cffmpeg_decode::setSaveFileDirPath(QString fileDirPath) {
saveFileDirPath = fileDirPath;
}
bool Cffmpeg_decode::openSave() {
QDir dir;
if (!dir.exists(saveFileDirPath)) {
dir.mkdir(saveFileDirPath);
}
QString strName =
QString("/%1.h264")
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH-mm-ss"));
strName = saveFileDirPath + strName;
// const AVOutputFormat *ofmt = av_guess_format("mp4", NULL, NULL);
int ret = avformat_alloc_output_context2(
&m_formatContextSave, nullptr, nullptr, strName.toStdString().data());
if (ret < 0) {
// free();
qWarning() << "DecodeVideo Error";
return false;
}
// m_videoStreamOut->codecpar->codec_tag = 0;
// if (m_formatContextSave->oformat->flags & AVFMT_GLOBALHEADER) {
// m_formatContextSave->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
// // m_videoStreamOut->codecpar->extradata = (uint8_t *)av_malloc(1024);
// // m_videoStreamOut->codecpar->extradata_size = 0;
// }
// 创建并初始化AVIOContext以访问url所指示的资源。
ret = avio_open(&m_formatContextSave->pb, strName.toStdString().data(),
AVIO_FLAG_WRITE);
if (ret < 0) {
// free();
qWarning() << "DecodeVideo Error";
return false;
}
// 向媒体文件添加新流
m_videoStreamOut = avformat_new_stream(m_formatContextSave, nullptr);
if (!m_videoStreamOut) {
qWarning() << "DecodeVideo Error";
return false;
}
// 拷贝一些参数给codecpar赋值(这里使用编码器上下文进行赋值)
ret = avcodec_parameters_from_context(m_videoStreamOut->codecpar, decoderCtx);
if (ret < 0) {
// free();
qWarning() << "avcodec_parameters_from_context Failed";
return false;
}
// 写入文件头
ret = avformat_write_header(m_formatContextSave, nullptr);
if (ret < 0) {
// free();
qWarning() << "DecodeVideo Error";
return false;
}
m_writeHeader = true;
qDebug() << "开始录制视频!";
return true;
}
void Cffmpeg_decode::saveDone() {
if (m_formatContextSave && m_writeHeader) {
av_write_trailer(m_formatContextSave); // 写入文件尾
m_writeHeader = false;
}
// 关闭文件
if (m_formatContextSave && !(m_formatContextSave->flags & AVFMT_NOFILE)) {
avio_close(m_formatContextSave->pb);
// av_freep(m_videoStreamOut);
if (m_formatContextSave) {
avformat_free_context(m_formatContextSave);
m_formatContextSave = nullptr;
}
// m_videoStreamOut = nullptr;
}
}