1. 项目背景
下位机里安装里摄像头,并处理摄像头的实时数据。上位机通过rstp协议连接下位机,接收下位机传输的视频流数据。
2. 前言
ffmpeg是一个开源库,可制作跨平台视频播放器。
ffmpeg库的安装:《Linu下安装ffmpeg》
涉及的库有如下几个:
avcodec avformat swscale avutil avcodec-ffmpeg swresample
3. 实现思路
ffmpeg视频流播放,比较占用系统资源,所以要使用多线程技术处理耗时操作。
ffmpeg解析完成后,使用QPainter绘制图像
4. 代码主要实现部分
1、ffmpge初始化接口
int FFmpeg::initial(QString & url)
{
qDebug() << "FFmpeg initial start.";
int ret;
rtspURL=url;
const AVCodec *pCodec;
//初始化
//av_register_all(); //新版的不需要注册
avformat_network_init(); //支持网络流
//打开码流前,设置参数
AVDictionary *optionsDict = NULL;
av_dict_set(&optionsDict, "rtsp_transport", "tcp", 0);
av_dict_set(&optionsDict, "stimeout", "30000000", 0);
pFormatCtx = avformat_alloc_context(); //初始化AVFormatContext, 分配一块内存,保存视频属性信息
ret = avformat_open_input(&pFormatCtx, rtspURL.toStdString().c_str(), NULL, &optionsDict);
char buf[1024] = {0};
if (ret < 0)
{
qDebug() << "Can not open this file";
av_strerror(ret, buf, 1024);
return -1;
}
//查找流信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
qDebug() << "Unable to get stream info";
return -1;
}
//获取视频流ID
int i = 0;
videoStream = -1;
for (i = 0; i < pFormatCtx->nb_streams; i++)
{
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i;
break;
}
}
if (videoStream == -1)
{
qDebug() << "Unable to find video stream";
return -1;
}
//AVCodecParameters转AVCodecContext
pCodecCtx = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(pCodecCtx,pFormatCtx->streams[videoStream]->codecpar);
//查找解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL)
{
qDebug() << "Unsupported codec";
return -1;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
{
qDebug() << "Unable to open codec";
return -1;
}
//打印关于输入或输出格式的详细信息
av_dump_format(pFormatCtx, 0, rtspURL.toStdString().c_str(), 0);
//存储解码后AVFrame
pFrame = av_frame_alloc();
height = pCodecCtx->height;
width = pCodecCtx->width;
qDebug() << "FFmpeg initial successfully";
return 0;
}
2、ffmpeg解码接口
int FFmpeg::h264Decodec()
{
qDebug() << "h264Decodec start...";
//为packet分配内存
AVPacket * packet = NULL;
packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//循环 获取视频的数据
while (av_read_frame(pFormatCtx, packet) >= 0)
{
//停止读取视频流
if(h264DecodecStop)
{
//进行内存释放
av_packet_unref(packet);
break;
}
//当视频流的ID跟读取到的视频流ID一致的时候
if(packet->stream_index==videoStream)
{
//解码
int re = avcodec_send_packet(pCodecCtx, packet);
av_packet_unref(packet);
if(re!=0)
{
continue;
}
//从解码器中获取解码的输出数据,存入pFrame
re = avcodec_receive_frame(pCodecCtx, pFrame);
if(re!=0)
{
qDebug() << "avcodec_receive_frame failed !";
continue;
}
//初始化img_convert_ctx
struct SwsContext * img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BICUBIC, NULL, NULL, NULL);
if (img_convert_ctx == NULL)
{
break;
}
mutex.lock();
char *rgb = new char[width*height*3];
memset(rgb, 0 , width*height*3);
pictureData[0] = (uint8_t *)rgb;
int lines[AV_NUM_DATA_POINTERS] = {0};
lines[0] = width * 3;
//开始转换图像数据
int rs = 0;
rs = sws_scale(img_convert_ctx,
(const uint8_t**) pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
pictureData, lines);
if (img_convert_ctx)
{
sws_freeContext(img_convert_ctx);
img_convert_ctx = NULL;
}
mutex.unlock();
if (rs == -1)
{
qDebug() << "__________Can open to change to des imag_____________e\n";
return -1;
}
}
}
if (packet!=NULL)
{
av_packet_unref (packet);
packet = NULL;
}
if(pFrame!=NULL)
{
av_frame_free(&pFrame);
pFrame = NULL;
}
qDebug() << "h264Decodec end...";
return 0;
}
3、qt界面绘制接口
void Video::paintEvent(QPaintEvent *)
{
QPainter painter(this);
if(ffmpeg->mutex.tryLock(1000))
{
QImage image=QImage(ffmpeg->pictureData[0],ffmpeg->width,ffmpeg->height,QImage::Format_RGB888);
QPixmap pix = QPixmap::fromImage(image);
painter.drawPixmap(0, 0, 640, 480, pix);
update();
ffmpeg->mutex.unlock();
}
}