音视频播放器设计(三)——OpenGL绘制视频

前言

视频绘制使用的qt的QOpenGLWidget,QOpenGLWidget已经对OpenGL做了封装处理,这里主要介绍代码的处理。

YUV转RGB

sws_getContext

功能

多路码流转换,为每个不同的码流都指定一个不同的转换上下文。

函数

struct SwsContext *sws_getContext(
            int srcW,                      /* 输入图像的宽度 */
            int srcH,                      /* 输入图像的宽度 */
            enum AVPixelFormat srcFormat,  /* 输入图像的像素格式 */
            int dstW,                      /* 输出图像的宽度 */
            int dstH,                      /* 输出图像的高度 */
            enum AVPixelFormat dstFormat,  /* 输出图像的像素格式 */
            int flags,                     /* 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR */
            SwsFilter *srcFilter,          /* 输入图像的滤波器信息, 若不需要传NULL */
            SwsFilter *dstFilter,          /* 输出图像的滤波器信息, 若不需要传NULL */
            const double *param            /* 特定缩放算法需要的参数(?),默认为NULL */
            );

sws_getCachedContext

功能

用于一路码流转换。

函数

struct SwsContext *sws_getCachedContext(struct SwsContext *context,  
                                        int srcW,                          // 输入图像的宽度
                                        int srcH,                          // 输入图像的高度
                                        enum AVPixelFormat srcFormat,      // 输入图像的像素格式
                                        int dstW,                          // 输出图像的宽度
                                        int dstH,                          // 输出图像的高度
                                        enum AVPixelFormat dstFormat,      // 输出图像的像素格式
                                        int flags,                         // 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR
                                        SwsFilter *srcFilter,              // 输入图像的滤波器信息, 若不需要传NULL
                                        SwsFilter *dstFilter,              // 输出图像的滤波器信息, 若不需要传NULL
                                        const double *param);              // 特定缩放算法需要的参数,默认为NULL

sws_freeContext

功能

释放sws_getContext 。

函数

void sws_freeContext(struct SwsContext *swsContext);

sws_scale

功能

视频像素格式和分辨率的转换。

函数

int sws_scale(struct SwsContext *c,               // 转换格式的上下文
              const uint8_t *const srcSlice[],    // 输入图像的每个颜色通道的数据指针
              const int srcStride[],              // 输入图像的每个颜色通道的跨度
              int srcSliceY,                      // 图像处理区域的起始位置
              int srcSliceH,                      // 图像处理区域的行数
              uint8_t *const dst[],               // 输出图像信息: 输出的每个颜色通道数据指针
              const int dstStride[]);             // 输出图像信息: 每个颜色通道行字节数

代码处理

bool XFFmpeg::ToRGB(char *out, int outwidth, int outheight)
{
	mutex.lock();
    
    //未打开视频文件或者未解码
	if (!ic||!yuv)
	{
		mutex.unlock();
		return false;
	}
    
	AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;
    
    // 初始化一个SwsContext
	cCtx = sws_getCachedContext(cCtx, videoCtx->width,
		videoCtx->height,
		videoCtx->pix_fmt,       //输入像素格式
		outwidth, outheight,
		AV_PIX_FMT_BGRA,         //输出像素格式
		SWS_BICUBIC,             //转码的算法
		NULL, NULL, NULL);
	if (!cCtx)
	{
		mutex.unlock();
		printf("sws_getCachedContext  failed!\n");
		return false;
	}
    
	uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
	data[0] = (uint8_t *)out;                   //第一位输出RGB
	int linesize[AV_NUM_DATA_POINTERS] = { 0 };
	linesize[0] = outwidth * 4;                 //一行的宽度,32位4个字节
    
    // 开始转码
	int h = sws_scale(cCtx, yuv->data,   //当前处理区域的每个通道数据指针
		yuv->linesize,                   //每个通道行字节数
		0, videoCtx->height,             //原视频帧的高度
		data,                            //输出的每个通道数据指针	
		linesize                         //每个通道行字节数
		);
	if (h > 0)
	{
		printf("(%d)", h);
	}
    
	mutex.unlock();
	return true;
}

视频绘制

视频帧的绘制使用的是Qt的paintEvent事件。

构造函数

VideoWidget::VideoWidget(QWidget *parent) :QOpenGLWidget(parent)
{
    // 设置定时器
	startTimer(20);
    
    // 启动线程读取数据帧
	XVideoThread::getInstance()->start();
}

paintEvent

void VideoWidget::paintEvent(QPaintEvent *e)
{
	static QImage *image = nullptr;
	static int w = 0;
	static int h = 0;
	if (w != width() || h != height())//当缩小窗口或者方法窗口时,删除image,重新绘制
	{
		if (image)
		{
			delete image->bits();
		    delete image;
			image = nullptr;
		}
	}
    
	if (nullptr == image)
	{
        //存放解码后的视频空间
		uchar *buf = new uchar[width()*height() * 4];
		image = new QImage(buf, width(), height(), QImage::Format_ARGB32);
	}

	//将解码后的视频帧转化为RGB
	XFFmpeg::getInstance()->ToRGB((char *)image->bits(), width(), height());

    //绘制FFMpeg解码后的视频
	QPainter painter;
	painter.begin(this);//清理屏幕
	painter.drawImage(QPoint(0, 0), *image);
	painter.end();
}

读取线程

void XVideoThread::run()
{
	char out[10000] = {0};
	while (!bExit) 
	{
        // 如果为暂停状态,不处理
		if (!XFFmpeg::getInstance()->bPlay)
		{
			msleep(10);
			continue;
		}
        
        // 确定list中是否有AVpacket包
		while (videos.size() > 0)
		{
			AVPacket pack = videos.front();                     // 每次取出list中的第一个AVPack包
			int pts = XFFmpeg::getInstance()->GetPts(&pack);    // 获得该包的pts
			if (pts > apts)                                     // 若视频包大于音频包的pts,结束
			{
				break;
			}
            
            // 解码视频帧
	 		XFFmpeg::getInstance()->Decode(&pack);
            
            // 清理该AVPacket包
			av_packet_unref(&pack);
            
            // 从list链表中删除
			videos.pop_front();
		}

        // 获取缓冲区大小
		int free = XAudioPlay::getInstance()->GetFree();
		if (free < 10000)
		{
			msleep(1);
			continue;
		}
        
        // 读取数据帧
		AVPacket pkt = XFFmpeg::getInstance()->Read();
		if (pkt.size <= 0)
		{
			msleep(10);
			continue;
		}
        
		if (pkt.stream_index == XFFmpeg::Get()->audioStream)
		{
            // 解码音频
			apts = XFFmpeg::getInstance()->Decode(&pkt);
            
            // 释放pkt包
			av_packet_unref(&pkt);
            
            // 重采样音频
			int len = XFFmpeg::getInstance()->ToPCM(out);
            
            //写入音频
			XAudioPlay::getInstance()->Write(out, len);
            
			continue;
		}

		videos.push_back(pkt);
	}
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值