Qt+FFmpeg工具对Rtsp协议实时视频流进行解码,并显示在QGraphicsView视图上
前言
提示:这里可以添加本文要记录的大概内容:
Qt采用的版本为5.14.2,FFmpeg工具为官网下载的64位最新版,标识号为“3998820”,调用函数为avcodec_version()。FFmpeg版本之间差别还是挺大的,对内部的许多函数进行了弃用和修改。
提示:以下是本篇文章正文内容,下面案例可供参考
一、主要是思想过程
创新读取Rstp流的新类继承QThread,利用FFmpeg内置函数拉流进行解码,编码为RGB数据后转换为QImage形式,通过Qt发送信号的方式将其发送到显示类的槽函数中,槽函数接受到帧后显示到QGraphicsView上,利用QTimer定时器不断刷新槽函数接收到的图片实现整体实时视频流展现。
二、使用步骤
1.引入FFmpeg库
测试时打印的数据较多,不修改了直接拿上原函数,FFmpeg主要是弃用了以往一些初始化函数和处理函数,但大致流程相同。通过访问摄像头的ip网址修改编码流为H.264,有些输出可能为H.265。packet->stream_index这个是当时比较难受的一个变量 ,因为一直会在0,1这两条数据流当中交换,后来查了下应该是当前传过来的一条是视频流,一条是音频流。
一开始断点还很好奇为什么会是1,导致视频流无法显示,后来一次测试才发现他是0,1交替读取,才输出了视频流。顺带附上FFmpeg解码线程的启动
void Video_ffm::RendStream()
{
int videoStream, numBytes;
videoStream = -1;
avformat_network_init();
pFormatCtx = new AVFormatContext();
char url[] = "rtsp://用户名:密码@IP地址:554/Streaming/Channels/101";
pFormatCtx = avformat_alloc_context();
AVDictionary* avdic = NULL;
// char option_key[]="rtsp_transport";
// char m_bTcp;
// av_dict_set(&avdic,option_key,m_bTcp?"udp":"tcp",0);
av_dict_set(&avdic, "stimeout", "30", 0); //设置超时断开连接时间
av_dict_set(&avdic, "rtsp_transport", "tcp", 0); //如果以tcp方式打开将udp替换为tcp
//画质优化
if (avformat_open_input(&pFormatCtx, url, nullptr, &avdic) == 0) //打开多媒体并获取信息
{
qDebug() << QString::fromLocal8Bit("能打开引接视频地址");
}
else
{
qDebug() << "没能打开引接视频地址";
}
if (avformat_find_stream_info(pFormatCtx, NULL) >= 0)//用于给每个媒体流的AVStream结构体赋值
{
qDebug() << "2222"<<QString::fromLocal8Bit("能打开引接视频地址");
}
if (avdic != NULL)
{
av_dict_free(&avdic);//释放字典内存
}
for (unsigned i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
qDebug() << "--------videoStream00-------------" << videoStream;
break;
}
}
if (videoStream == -1)
{
qDebug() << "Didn't find a video stream.\n";
return;
}
av_dump_format(pFormatCtx, 0, url, 0); //打印出当前的输入流信息 注意:最后一个参数填0,打印输入流;最后一个参数填1,打印输出流
//查找解码器,获取指向视频流的编解码器上下文的指针
pCodecCtx = new AVCodecContext();
pCodecCtx = avcodec_alloc_context3(nullptr);
//将音频流结构体拷贝给编解码结构体
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);
//通过解封装之后从avstream结构体里获取CodecID(指定格式流)
pCodec = new AVCodec();
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
//打印一些视频信息
qDebug() << "-------Struct-------\n"
<< "-----\n" << pCodec
<< "-----\n" << pCodecCtx->codec_id
<< "-----\n" << videoStream;
//设置编码器参数(不同参数对视频编质量或大小的影响)
// pCodecCtx->bit_rate =0; //初始化为0 比特率
// pCodecCtx->time_base.num=1; //下面两行:一秒钟25帧
// pCodecCtx->time_base.den=25;
if (pCodec == nullptr) // 找不到解码器
{
qDebug() << "]]]]]]]]]]]]]]]]]]]]]]]]]";
}
if (avcodec_open2(pCodecCtx, pCodec, nullptr) < 0) // 无法打开解码器
{
qDebug() << "]]]]]]]]]]]]]]]]]]]]]]]]]";
return;
}
pFrame = new AVFrame();
pFrameRGB = new AVFrame();
pFrame = av_frame_alloc(); //创建 存储解码器信息*/
pFrameRGB = av_frame_alloc();
static struct SwsContext* ctx_convert_img;
static uint8_t* out_buffer;
//解码后的h.264数据转换成RGB32,方便利用Qt显示
ctx_convert_img = sws_getContext(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);
numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
/*瓜分分配的空间*/
//瓜分上一步分配到的buffer.
out_buffer = new uint8_t();
out_buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, out_buffer, AV_PIX_FMT_RGB32, pCodecCtx->width, pCodecCtx->height, 1);
//int y_size = pCodecCtx->width * pCodecCtx->height;
packet = (AVPacket*)malloc(sizeof(AVPacket)); //申请一个视频帧包的大小
//av_new_packet(packet, y_size); //分配packet的数据,为packet分配一个指定大小的内存,设置为packet->stream_index=0
qDebug() << "---------packet->stream_index---------" << packet->stream_index;
while (av_read_frame(pFormatCtx, packet) >= 0)
{
// 此处代码负责读取每一帧
// qDebug() << "---------av_read_frame---------" << av_read_frame(pFormatCtx, packet);
qDebug() << "---------packet->stream_index---------" << packet->stream_index;
// av_read_frame(pFormatCtx, packet);
qDebug() << "---------packet->stream_index---------" << packet->stream_index;
if (packet->stream_index == videoStream)
{
ret = avcodec_send_packet(pCodecCtx, packet); //发送数据到ffmepg,放到解码队列中
got_picture = avcodec_receive_frame(pCodecCtx, pFrame); //将成功的解码队列中取出1个frame
if (ret < 0)
{
usleep(1000);
printf("decode error.\n");
continue;
}
if (!got_picture)
{
//颜色空间转换,最后输出到out_buffer
sws_scale(ctx_convert_img, (uint8_t const* const*)pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);//sws_scale库可以在一个函数里面同时实现:1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。
//把这个RGB数据 用QImage加载
QImage tmpImg((uchar*)out_buffer, pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32);
QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
emit sig_GetOneFrame(image); //发送信号
}
}
av_packet_unref(packet); // 释放packet资源
}
// 释放资源
av_frame_free(&pFrame);
avcodec_free_context(&pCodecCtx);
avformat_close_input(&pFormatCtx);
qDebug() << "=================wdqwdqwd";
}
总结
以上就是Qt+FFmpeg工具对Rtsp协议实时视频流进行解码,并显示在QGraphicsView视图上的介绍。,上方为一个简易的视频播放器,功能截图如下: