|
|
#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;
|
|
|
// }
|
|
|
firstDts = AV_NOPTS_VALUE; // 初始化第一帧的DTS
|
|
|
startTime = av_gettime();
|
|
|
// 读取数据包
|
|
|
while (av_read_frame(inputFormatCtx, inputPacket) >= 0) {
|
|
|
if (IsstopPlay) {
|
|
|
qDebug() << "video play stop";
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 第一次获取时间设置起始DTS
|
|
|
if (firstDts == AV_NOPTS_VALUE) {
|
|
|
firstDts = inputPacket->dts;
|
|
|
startTime = av_gettime(); // 记录第一个包到来的系统时间
|
|
|
}
|
|
|
|
|
|
if (inputPacket->stream_index == videoStreamIndex) {
|
|
|
// 推流
|
|
|
if (bPushStreamFlag) {
|
|
|
// av_packet_clone(inputPacket);
|
|
|
AVPacket *outputPacket = av_packet_clone(inputPacket);
|
|
|
emit sendStreamData_Signal(outputPacket, frm_cnt, startTime, firstDts);
|
|
|
}
|
|
|
// qDebug() << "******拉流" << QString::number(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);
|
|
|
|
|
|
// 停止推流
|
|
|
bPushStreamFlag = false;
|
|
|
emit sendStopPushStream_Signal();
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
}
|
|
|
}
|