1背景
在17里面整理了下之前调用的顺序,如下
- 线程start中调用了run
- 线程run中执行了doTask
- 子类hffplayer的dotask中解析并给data赋值
- hframe在open的时候指向了data
- push_frame(&hframe);
- HVideoPlayer中使用HFrameBuf
- HFrameBuf使用了HRingBuf
- HRingBuf继承了HBuf
在16结尾处理了HRingBuf,这一篇继续之后的处理
2 步骤
2.1 HFrameBuf
右键添加新文件
然后在头文件追加如下代码
class HFrameBuf : public HRingBuf
{
public:
HFrameBuf();
int push(HFrame* pFrame);
int pop(HFrame* pFrame);
void clear();
int cache_num;
std::deque<HFrame> frames;
std::mutex mutex;
};
然后选中 push,pop,clear创建定义
然后添加如下代码
#include "hframebuf.h"
HFrameBuf::HFrameBuf() {
cache_num=DEFAUTL_FRAME_CACHENUM;
}
int HFrameBuf::push(HFrame *pFrame)
{
std::lock_guard<std::mutex> locker(this->mutex);
if(pFrame->isNull())
return -10;
if(frames.size()>=(size_t)cache_num){
HFrame& frame=frames.front();
frames.pop_front();
free(frame.buf.len);
if(frame.userdata){
::free(frame.userdata);
frame.userdata=NULL;
}
}
int ret=0;
if(isNull()){
resize(pFrame->buf.len*cache_num);
ret=1;
}
HFrame frame;
frame.buf.base=alloc(pFrame->buf.len);
frame.buf.len=pFrame->buf.len;
frame.copy(*pFrame);
frames.push_back(frame);
return ret;
}
int HFrameBuf::pop(HFrame *pFrame)
{
std::lock_guard<std::mutex> locker(this->mutex);
if(isNull())
return -10;
if(frames.size()==0){
return -20;
}
HFrame& frame=frames.front();
frames.pop_front();
free(frame.buf.len);
if(frame.isNull())return -30;
pFrame->copy(frame);
return 0;
}
void HFrameBuf::clear()
{
std::lock_guard<std::mutex> locker(this->mutex);
frames.clear();
HRingBuf::clear();
}
其他在hbuf.h中需要补充
bool isNull();
void copy(void* data,size_t len);
在hbuf.cpp中需要补充
void HBuf::copy(void *data, size_t len)
{
resize(len);
memcmp(base,data,len);
}
bool HBuf::isNull()
{
return base==NULL||len==0;
}
在hframe.h中补充
uint64_t ts;
int64_t useridx;
void* userdata;
HFrame(){
w=h=bpp=type=0;
ts=0;
this->useridx=-1;
this->userdata=NULL;
}
bool isNull();
void copy(const HFrame& rhs);
在hframe.cpp中补充
bool HFrame::isNull()
{
return w==0||h==0||buf.isNull();
}
void HFrame::copy(const HFrame &rhs)
{
w=rhs.w;
this->h=rhs.h;
this->bpp=rhs.bpp;
this->type=rhs.type;
this->ts=rhs.ts;
this->useridx=rhs.useridx;
this->userdata=rhs.userdata;
buf.copy(rhs.buf.base,rhs.buf.len);
}
2.2 start开启线程后的doPrepare,还有后续的run
hvideoplayer.h 中补充如下
virtual int stop()=0;
virtual int pause()=0;
virtual int resume()=0;
void clear_frame_cache(){
this->frame_buf.clear();
}
int push_frame(HFrame* pFrame){
return frame_buf.push(pFrame);
}
int pop_frame(HFrame* pFrame){
return frame_buf.pop(pFrame);
}
protected:
HFrameBuf frame_buf;
hthread.h 中补充如下
public:
enum Status{
STOP,
RUNNING,
PAUSE
};
virtual int start(){
if(status==STOP){
thread = std::thread([this]{
if(!doPrepare())return;
setStatus(RUNNING);
run();
setStatus(STOP);
});
}
return 0;
}
void setStatus(Status stat){
status=stat;
}
virtual void run(){
while(status!= STOP){
doTask();
}
}
virtual bool doPrepare(){
return true;
}
virtual void doTask(){};
virtual int stop() {
if (status != STOP) {
setStatus(STOP);
}
if (thread.joinable()) {
thread.join(); // wait thread exit
}
return 0;
}
virtual int pause() {
if (status == RUNNING) {
setStatus(PAUSE);
}
return 0;
}
virtual int resume() {
if (status == PAUSE) {
setStatus(RUNNING);
}
return 0;
}
std::thread thread;
std::atomic<Status> status;
};
hffplayer.h 中重载doTask
// HThread interface
public:
virtual void doTask() override;
int quit;
// HVideoPlayer interface
public:
virtual int stop() override;
virtual int pause() override;
virtual int resume() override;
然后doTask重构添加定义 hffplayer.cpp
void HFFPlayer::doTask()
{
while (!this->quit) {
av_init_packet(packet);
int ret=av_read_frame(this->fmt_ctx,this->packet);
if(ret!=0){
if(!this->quit){
}
av_packet_unref(this->packet);
return;
}
if(this->packet->stream_index!= this->video_stream_index){
continue;
}
ret=avcodec_send_packet(this->codec_ctx,this->packet);
if(ret!=0){
av_packet_unref(this->packet);
return;
}
ret= avcodec_receive_frame(this->codec_ctx,this->frame);
if(ret!=0){
}else{
break;
}
}
if(this->sws_ctx){
int h=sws_scale(this->sws_ctx,this->frame->data,
this->frame->linesize,0,this->frame->height,this->data,this->linesize);
if(h<=0 || h != this->frame->height){
return;
}
}
this->push_frame(&this->hframe);
}
int HFFPlayer::stop()
{
return HThread::stop();
}
int HFFPlayer::pause()
{
return HThread::pause();
}
int HFFPlayer::resume()
{
return HThread::resume();
}
上面打断点并运行
2.3 gl渲染 的类库
参考ffmpeg播放器10 中glew的,分别添加include以及lib和bin下面,到项目文件夹
xplayer/3rd/bin/msvc/glew32.dll
xplayer/3rd/include/GL/
xplayer/3rd/lib/msvc/glew32.lib
xplayer/3rd/lib/msvc/glew32s.lib
然后在pro中增加如下
INCLUDEPATH +=$$PWD/3rd/include
INCLUDEPATH +=$$PWD/3rd/include/GL
win32 {
win32-msvc{
LIBS += -lavformat \
-lavdevice \
-lavcodec \
-lswresample \
-lswscale \
-lavutil \
LIBS += -lopengl32 -lglu32
LIBS += -lglew32
DESTDIR = $$PWD/3rd/bin/msvc
LIBS += -L$$PWD/3rd/lib/msvc
}
}
2.4 一系列修改
typedef enum{
PIX_FMT_NONE = 0,
PIX_FMT_GRAY, // YYYYYYYY
PIX_FMT_YUV_FIRST = 100,
PIX_FMT_YUV_PLANAR_FIRST = 200,
PIX_FMT_IYUV, // YYYYYYYYUUVV
PIX_FMT_YV12, // YYYYVVYYVVUU
PIX_FMT_NV12, // YYUVYYYYUVUV
PIX_FMT_NV21, // YYVUYYYYVUVU
PIX_FMT_YUV_PLANAR_LAST,
PIX_FMT_YUV_PACKED_FIRST = 300,
PIX_FMT_YUY2, // YUYVYUYV
PIX_FMT_YVYU, // YVYUYVYU
PIX_FMT_UYVY, // UYVYUYVY
PIX_FMT_YUV_PACKED_LAST,
PIX_FMT_YUV_LAST,
PIX_FMT_RGB_FIRST = 400,
PIX_FMT_RGB, // RGBRGB
PIX_FMT_BGR, // BGRBGR
PIX_FMT_RGBA, // RGBARGBA
PIX_FMT_BGRA, // BGRABGRA
PIX_FMT_ARGB, // ARGBARGB
PIX_FMT_ABGR, // ABGRABGR
PIX_FMT_RGB_LAST,
} pix_fmt_e;
hbuf.cpp 修改copy代码如下
void HBuf::copy(void *data, size_t len)
{
resize(len);
memcpy(base,data,len);
}
glwnd.h 添加paintGL
// QOpenGLWidget interface
protected:
virtual void paintGL() override;
glwnd.cpp添加定义
void GLWnd::paintGL()
{
HGLWidget::paintGL();
if(this->last_frame.isNull()){
}else{
drawFrame(&this->last_frame);
}
}
hframebuf.cpp 修改pop实现
int HFrameBuf::pop(HFrame *pFrame)
{
std::lock_guard<std::mutex> locker(this->mutex);
if(isNull())
return -10;
if(frames.size()==0){
return -20;
}
HFrame frame = std::move(frames.front());
frames.pop_front();
if(frame.isNull()){
return -30;
}
pFrame->copy(frame);
return 0;
}
hvideoplayer.h 中添加fps,并初始化
#define DEFAULT_FPS 25
int fps;
HVideoPlayer::HVideoPlayer() {
width = 0;
height = 0;
duration = 0;
start_time = 0;
eof = 0;
error = 0;
fps=DEFAULT_FPS;
}
hvideowidget.cpp 修改onOpenSucceed方法
void HVideoWidget::onOpenSucceed()
{
timer->start(1000 / (pImpl_player->fps));
setAspectRatio(aspect_ratio);
}
2.5 hglwidget.h的修改
#ifndef HGLWIDGET_H
#define HGLWIDGET_H
#include "glew.h"
#include "QtOpenGLWidgets/qopenglwidget.h"
#include "hframe.h"
typedef struct GLTexture_s {
unsigned int id; // for glGenTextures
HFrame frame;
} GLTexture;
#define CLR_B(c) (c & 0xff)
#define CLR_G(c) ((c >> 8) & 0xff)
#define CLR_R(c) ((c >> 16) & 0xff)
#define CLR_A(c) ((c >> 24) & 0xff)
#define ARGB(a, r, g, b) MAKE_FOURCC(a, r, g, b)
#ifndef ABS
#define ABS(n) ((n) > 0 ? (n) : -(n))
#endif
#define FLOAT_PRECISION 1e-6
#define FLOAT_EQUAL_ZERO(f) (ABS(f) < FLOAT_PRECISION)
void bindTexture(GLTexture* tex, QImage* img);
class HGLWidget : public QOpenGLWidget
{
public:
explicit HGLWidget(QWidget *parent = nullptr);
// ratio = 0 means spread
void setAspectRatio(double ratio);
void drawFrame(HFrame *pFrame);
void drawTexture(QRect rc, GLTexture *tex);
void drawRect(QRect rc, QColor clr, int line_width = 1, bool bFill = false);
void drawText(QPoint lb, const char* text, int fontsize, QColor clr);
signals:
// QOpenGLWidget interface
protected:
// QOpenGLWidget interface
protected:
virtual void initializeGL() override;
virtual void resizeGL(int w, int h) override;
virtual void paintGL() override;
void setVertices(double ratio);
void setVertices(QRect rc);
static void loadYUVShader();
void initVAO();
void initYUV();
void drawYUV(HFrame* pFrame);
protected:
static std::atomic_flag s_glew_init;
static GLuint prog_yuv;
static GLuint texUniformY;
static GLuint texUniformU;
static GLuint texUniformV;
GLuint tex_yuv[3];
double aspect_ratio;
GLfloat vertices[8];
GLfloat textures[8];
// NOTE: QPainter used 3 VertexAttribArray
enum VER_ATTR {
VER_ATTR_VER = 3,
VER_ATTR_TEX,
};
};
#endif // HGLWIDGET_H
以及cpp中的实现
#include "hglwidget.h"
#include "avdef.h"
static int glPixFmt(int type) {
switch (type) {
case PIX_FMT_BGR: return GL_BGR;
case PIX_FMT_RGB: return GL_RGB;
case PIX_FMT_BGRA: return GL_BGRA;
case PIX_FMT_RGBA: return GL_RGBA;
}
return -1;
}
void bindTexture(GLTexture* tex, QImage* img) {
if (img->format() != QImage::Format_ARGB32)
return;
glGenTextures(1, &tex->id);
glBindTexture(GL_TEXTURE_2D, tex->id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
tex->frame.w = img->width();
tex->frame.h = img->height();
tex->frame.type = GL_BGRA;
tex->frame.bpp = img->bitPlaneCount();
// gluBuild2DMipmaps(GL_TEXTURE_2D, tex->frame.bpp/8, tex->frame.w, tex->frame.h, tex->frame.type, GL_UNSIGNED_BYTE, img->bits());
glTexImage2D(GL_TEXTURE_2D, 0, tex->frame.bpp/8, tex->frame.w, tex->frame.h, 0, tex->frame.type, GL_UNSIGNED_BYTE, img->bits());
}
HGLWidget::HGLWidget(QWidget *parent)
: QOpenGLWidget{parent}
{
aspect_ratio = 0.0;
setVertices(1.0);
GLfloat tmp[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
// reverse
/*
GLfloat tmp[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
};
*/
memcpy(textures, tmp, sizeof(GLfloat)*8);
}
std::atomic_flag HGLWidget::s_glew_init = ATOMIC_FLAG_INIT;
GLuint HGLWidget::prog_yuv;
GLuint HGLWidget::texUniformY;
GLuint HGLWidget::texUniformU;
GLuint HGLWidget::texUniformV;
void HGLWidget::initializeGL()
{
if (glewInit() != 0) {
s_glew_init.clear();
qFatal("glewInit failed");
return;
}
initVAO();
loadYUVShader();
initYUV();
}
void HGLWidget::setAspectRatio(double ratio) {
aspect_ratio = ratio;
if (aspect_ratio==0) {
setVertices(1.0);
}
else {
setVertices((double)height()/(double)width() * aspect_ratio);
}
}
void HGLWidget::setVertices(double ratio) {
GLfloat w = 1.0, h = 1.0;
if (ratio < 1.0) {
w = ratio;
}
else {
h = 1.0 / ratio;
}
GLfloat tmp[] = {
-w, -h,
w, -h,
-w, h,
w, h,
};
memcpy(vertices, tmp, sizeof(GLfloat)*8);
}
void HGLWidget::setVertices(QRect rc) {
int wnd_w = width();
int wnd_h = height();
if (wnd_w <= 0 || wnd_h <= 0) {
return;
}
GLfloat left = (GLfloat)rc.left() * 2 / wnd_w - 1;
GLfloat right = (GLfloat)(rc.right()+1) * 2 / wnd_w - 1;
GLfloat top = 1 - (GLfloat)rc.top() * 2 / wnd_h;
GLfloat bottom = 1 - (GLfloat)(rc.bottom()+1) * 2 / wnd_h;
qDebug("l=%d r=%d t=%d b=%d", rc.left(), rc.right(), rc.top(), rc.bottom());
qDebug("l=%f r=%f t=%f b=%f", left, right, top, bottom);
GLfloat tmp[] = {
left, bottom,
right, bottom,
left, top,
right, top
};
memcpy(vertices, tmp, sizeof(GLfloat)*8);
}
void HGLWidget::loadYUVShader() {
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
char szVS[] = " \
attribute vec4 verIn; \
attribute vec2 texIn; \
varying vec2 texOut; \
\
void main(){ \
gl_Position = verIn; \
texOut = texIn; \
} \
";
const GLchar* pszVS = szVS;
GLint len = strlen(szVS);
glShaderSource(vs, 1, (const GLchar**)&pszVS, &len);
char szFS[] = " \
varying vec2 texOut; \
uniform sampler2D tex_y; \
uniform sampler2D tex_u; \
uniform sampler2D tex_v; \
\
void main(){ \
vec3 yuv; \
vec3 rgb; \
yuv.x = texture2D(tex_y, texOut).r; \
yuv.y = texture2D(tex_u, texOut).r - 0.5; \
yuv.z = texture2D(tex_v, texOut).r - 0.5; \
rgb = mat3( 1, 1, 1, \
0, -0.39465, 2.03211, \
1.13983, -0.58060, 0) * yuv; \
gl_FragColor = vec4(rgb, 1); \
} \
";
const GLchar* pszFS = szFS;
len = strlen(szFS);
glShaderSource(fs, 1, (const GLchar**)&pszFS, &len);
glCompileShader(vs);
glCompileShader(fs);
//#ifdef _DEBUG
GLint iRet = 0;
glGetShaderiv(vs, GL_COMPILE_STATUS, &iRet);
qDebug("vs::GL_COMPILE_STATUS=%d", iRet);
glGetShaderiv(fs, GL_COMPILE_STATUS, &iRet);
qDebug("fs::GL_COMPILE_STATUS=%d", iRet);
//#endif
prog_yuv = glCreateProgram();
glAttachShader(prog_yuv, vs);
glAttachShader(prog_yuv, fs);
glBindAttribLocation(prog_yuv, VER_ATTR_VER, "verIn");
glBindAttribLocation(prog_yuv, VER_ATTR_TEX, "texIn");
glLinkProgram(prog_yuv);
//#ifdef _DEBUG
glGetProgramiv(prog_yuv, GL_LINK_STATUS, &iRet);
qDebug("prog_yuv=%d GL_LINK_STATUS=%d", prog_yuv, iRet);
//#endif
glValidateProgram(prog_yuv);
texUniformY = glGetUniformLocation(prog_yuv, "tex_y");
texUniformU = glGetUniformLocation(prog_yuv, "tex_u");
texUniformV = glGetUniformLocation(prog_yuv, "tex_v");
qDebug("loadYUVShader ok");
}
void HGLWidget::initVAO() {
glVertexAttribPointer(VER_ATTR_VER, 2, GL_FLOAT, GL_FALSE, 0, vertices);
glEnableVertexAttribArray(VER_ATTR_VER);
glVertexAttribPointer(VER_ATTR_TEX, 2, GL_FLOAT, GL_FALSE, 0, textures);
glEnableVertexAttribArray(VER_ATTR_TEX);
}
void HGLWidget::initYUV() {
glGenTextures(3, tex_yuv);
for (int i = 0; i < 3; ++i) {
glBindTexture(GL_TEXTURE_2D, tex_yuv[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
void HGLWidget::resizeGL(int w, int h) {
glViewport(0,0,w,h);
setAspectRatio(aspect_ratio);
}
void HGLWidget::paintGL() {
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
void HGLWidget::drawYUV(HFrame* pFrame) {
assert(pFrame->type == PIX_FMT_IYUV || pFrame->type == PIX_FMT_YV12);
int w = pFrame->w;
int h = pFrame->h;
int y_size = w*h;
GLubyte* y = (GLubyte*)pFrame->buf.base;
GLubyte* u = y + y_size;
GLubyte* v = u + (y_size>>2);
if (pFrame->type == PIX_FMT_YV12) {
GLubyte* tmp = u;
u = v;
v = tmp;
}
glUseProgram(prog_yuv);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex_yuv[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, y);
glUniform1i(texUniformY, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, tex_yuv[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w/2, h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, u);
glUniform1i(texUniformU, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, tex_yuv[2]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w/2, h/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, v);
glUniform1i(texUniformV, 2);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glUseProgram(0);
}
void HGLWidget::drawFrame(HFrame *pFrame) {
if (pFrame->type == PIX_FMT_IYUV || pFrame->type == PIX_FMT_YV12) {
drawYUV(pFrame);
}
else {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glRasterPos3f(-1.0f,1.0f,0);
glPixelZoom(width()/(float)pFrame->w, -height()/(float)pFrame->h);
glDrawPixels(pFrame->w, pFrame->h, glPixFmt(pFrame->type), GL_UNSIGNED_BYTE, pFrame->buf.base);
}
}
void HGLWidget::drawTexture(QRect rc, GLTexture *tex) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width(), height(), 0.0, -1.0, 1.0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->id);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBegin(GL_QUADS);
glTexCoord2d(0,0);glVertex2i(rc.left(), rc.top());
glTexCoord2d(1,0);glVertex2i(rc.right(), rc.top());
glTexCoord2d(1,1);glVertex2i(rc.right(), rc.bottom());
glTexCoord2d(0,1);glVertex2i(rc.left(), rc.bottom());
glEnd();
glDisable(GL_TEXTURE_2D);
glDisable(GL_BLEND);
}
void HGLWidget::drawRect(QRect rc, QColor clr, int line_width, bool bFill) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, width(), height(), 0.0, -1.0, 1.0);
if (bFill) {
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
else {
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
glLineWidth(line_width);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4ub(clr.red(), clr.green(), clr.blue(), clr.alpha());
glRecti(rc.left(), rc.top(), rc.right(), rc.bottom());
glColor4ub(255,255,255,255);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDisable(GL_BLEND);
}
#include <QPainter>
void HGLWidget::drawText(QPoint lb, const char* text, int fontsize, QColor clr) {
QPainter painter(this);
QFont font = painter.font();
font.setPointSize(fontsize);
painter.setFont(font);
painter.setPen(clr);
painter.drawText(lb, text);
}
2.6 最后效果(只有一帧,doTask中只处理了一次)
当前代码分支