#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(); m_rtsp_transport = "tcp"; } 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::setStreamUrl(QString url) { _url = url; } void Cffmpeg_decode::setPlayVideo(bool bPlay) { mutex.lock(); bPlayVideoFlag = bPlay; if (bPlayVideoFlag) { // 初始化 previous_pts_time 为无效值(拉流使用) previous_pts_time = -1.0; // 表示没有上一帧的时间戳 first_frame_pts_time = 0.0; // 第一帧的 PTS 时间 first_frame_system_time = 0.0; // 第一帧解码时的系统时间 } mutex.unlock(); } /** * @brief 设置推流 * @param bPushStream: 开启/关闭推流 */ void Cffmpeg_decode::setPushStream(bool bPushStream) { mutex.lock(); bPushStreamFlag = bPushStream; if (bPushStreamFlag) { // 推流初始化 // emit sendInitPushStream_Signal(inputFormatCtx); } else { // 停止推流 emit sendStopPushStream_Signal(); bOpenPushStreamFlag = false; firstDts = AV_NOPTS_VALUE; // 初始化第一帧的DTS } mutex.unlock(); } bool Cffmpeg_decode::open_input_file() { if (_url.isEmpty()) return 0; if (!m_bSetRtspTransport) { m_rtsp_transport = _url.left(4) == "rtmp" ? "tcp" : "udp"; } //========================解码============================ AVDictionary *avdic = NULL; // 如果设置失败,则设置UDP传输 if (m_rtsp_transport == "udp") av_dict_set(&avdic, "rtsp_transport", "udp", 0); else av_dict_set(&avdic, "rtsp_transport", "tcp", 0); // 设置缓存大小,1080p可将值调大 av_dict_set(&avdic, "buffer_size", "4096000", 0); // 设置超时断开连接时间,单位微秒//listen_timeout // av_dict_set(&avdic, "listen_timeout", "200000", 0); av_dict_set(&avdic, "stimeout", "3000000", 0); // 设置超时3秒 av_dict_set(&avdic, "max_delay", "300000", 0); // 设置最大时延300ms av_dict_set(&avdic, "tune", "zerolatency", 0); // 实时编码 av_dict_set(&avdic, "preset", "faster", 0); // ultrafast av_dict_set(&avdic, "threads", "auto", 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 (uint i = 0; i < inputFormatCtx->nb_streams; i++) { if (inputFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } 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; } // 设置加速解码 decoderCtx->lowres = decoder->max_lowres; decoderCtx->flags2 |= AV_CODEC_FLAG2_FAST; // 打开解码器 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; } // 裸流保存初始化 m_saveVideoFlag = openSave(); return true; } // 线程里持续执行 void Cffmpeg_decode::run() { if (!open_input_file()) { qDebug() << "Please open video file first."; emit sendConnectFail(1); IsstopPlay = true; return; } // 初始化 previous_pts_time 为无效值(拉流使用) previous_pts_time = -1.0; // 表示没有上一帧的时间戳 first_frame_pts_time = 0.0; // 第一帧的 PTS 时间 first_frame_system_time = 0.0; // 第一帧解码时的系统时间 // 推流使用 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 && bPushStreamFlag) { firstDts = inputPacket->dts; startTime = av_gettime(); // 记录第一个包到来的系统时间 } if (inputPacket->stream_index == videoStreamIndex) { // 推流 if (bPushStreamFlag) { if (!bOpenPushStreamFlag) { // 推流初始化 emit sendInitPushStream_Signal(inputFormatCtx); bOpenPushStreamFlag = true; // QThread::msleep(10); } AVPacket *outputPacket = av_packet_clone(inputPacket); emit sendStreamData_Signal(outputPacket, frm_cnt, startTime, firstDts); } // 保存裸流 if (m_saveVideoFlag) { // 由于保存的m_formatContextSave只创建了一个视频流,而读取到的图像的流索引不一定为0,可能会出现错误【Invalid // packet stream index: 1】 // 所以这里需要将stream_index指定为和m_formatContextSave中视频流索引相同,因为就一个流,所以直接设置为0 inputPacket->stream_index = 0; av_write_frame(m_formatContextSave, inputPacket); // 将数据包写入输出媒体文件 } if (bPlayVideoFlag) { // 解码数据包 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); } if (inputPacket->dts <= 0) continue; // 获取当前帧的 PTS(显示时间戳),并转换为相同时间基 double pts_time = av_rescale_q( inputPacket->pts, inputFormatCtx->streams[videoStreamIndex]->time_base, AVRational{1, AV_TIME_BASE}); // 如果是第一帧,记录系统时间和 PTS 时间 if (previous_pts_time == -1.0) { first_frame_pts_time = pts_time; first_frame_system_time = av_gettime(); // 系统时间(秒) previous_pts_time = pts_time; } // 转化为RGB图像并显示 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(1); // 计算从第一帧开始的相对 PTS 时间 double elapsed_pts_time = pts_time - first_frame_pts_time; // 计算从第一帧开始的相对系统时间 double elapsed_system_time = av_gettime() - first_frame_system_time; // 计算需要等待的时间(us) double wait_time = elapsed_pts_time - elapsed_system_time; // qDebug() << "pull stream sleep time:" // << QString::number(wait_time / 1000.0); if (wait_time > 0) { av_usleep(wait_time); // 延时以同步 PTS } // 更新 previous_pts_time 为当前帧的 PTS previous_pts_time = pts_time; } } } av_packet_unref(inputPacket); } frm_cnt++; if (IsstopPlay) { if (m_saveVideoFlag) { saveDone(); } break; } // 花屏 // if (av_read_frame(inputFormatCtx, inputPacket) < 0) { // break; // 达到文件末尾 // } } if (m_saveVideoFlag) { saveDone(); } // QCoreApplication::processEvents(); qDebug() << "All video play done"; } // 退出 void Cffmpeg_decode::stop() { IsstopPlay = true; if (!inputPacket) av_packet_free(&inputPacket); if (!yuvFrame) av_frame_free(&yuvFrame); if (!rgbFrame) av_frame_free(&rgbFrame); if (!encoderCtx) avcodec_close(encoderCtx); if (!encoderCtx) avcodec_free_context(&encoderCtx); if (!decoderCtx) avcodec_close(decoderCtx); if (!decoderCtx) avcodec_free_context(&decoderCtx); if (!inputFormatCtx) avformat_close_input(&inputFormatCtx); } /** * @brief 设置拉流视频保存路径 * @param fileDirPath: 拉流视频保存路径 */ void Cffmpeg_decode::setSaveFileDirPath(QString fileDirPath) { saveFileDirPath = fileDirPath; } /** * @brief 打开本地视频保存输出流 * @return */ 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() << "Open file Error"; return false; } // 向媒体文件添加新流 m_videoStreamOut = avformat_new_stream(m_formatContextSave, nullptr); if (!m_videoStreamOut) { qWarning() << "Create New Stream 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() << "avformat_write_header Error"; return false; } m_writeHeader = true; qDebug() << "保存视频文件初始化成功!"; return true; } /** * @brief 视频保存结束,释放资源 */ 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_saveVideoFlag = false; } void Cffmpeg_decode::setFlowType(QString param) { m_rtsp_transport = param; m_bSetRtspTransport = true; }