#include "XFFmpeg.h"
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "swscale.lib")
static double r2d(AVRational r)
{
return r.num == 0 || r.den == 0 ? 0. : (double)r.num / (double)r.den;
}
int XFFmpeg::Open(const char *path)
{
Close();
mutex.lock(); //考虑多线程, 尽量晚的锁,尽量早的释放
/*char *path = "video.mp4";*/
int re = avformat_open_input(&ic, path, 0, 0); // lib: avformat
if (re != 0)
{
mutex.unlock();
av_strerror(re, errorbuf, sizeof(errorbuf)); // lib: avutil
printf("open %s failed: %s\n", path, errorbuf);
return 0;
}
totalMs = ((ic->duration / AV_TIME_BASE) * 1000);
//找到解码器, 打开解码器
for (int i = 0; i < ic->nb_streams; i++)
{
//AVCodecContext: 解码器的值
AVCodecContext *enc = ic->streams[i]->codec;
if (enc->codec_type == AVMEDIA_TYPE_VIDEO)
{
videoStream = i; //记录视频流index
fps = r2d(ic->streams[i]->avg_frame_rate);
AVCodec *codec = avcodec_find_decoder(enc->codec_id);
if (!codec)
{
mutex.unlock();
printf("video code not find! \n");
return 0;
}
//打开解码器
int err = avcodec_open2(enc, codec, NULL);
if (err != 0)
{
mutex.unlock();
char buf[1024] = { 0 };
av_strerror(err, buf, sizeof(buf));
printf(buf);
return 0;
}
printf("open codec success!\n");
}
}
mutex.unlock();
return totalMs;
}
void XFFmpeg::Close()
{
mutex.lock();
if (ic) avformat_close_input(&ic);
if (yuv) av_frame_free(&yuv);
if (cCtx)
{
sws_freeContext(cCtx);
cCtx = NULL;
}
mutex.unlock();
}
std::string XFFmpeg::GetError()
{
mutex.lock();
std::string re = this->errorbuf; //返回出去的是一份复制的空间,不会造成多线程访问出现异常
mutex.unlock();
return re;
}//多线程安全, 复制一份
AVPacket XFFmpeg::Read()
{
AVPacket pkt;
memset(&pkt, 0, sizeof(AVPacket));
mutex.lock();
if (!ic)
{
mutex.unlock();
return pkt;
}
int err = av_read_frame(ic, &pkt);
if (err != 0)
{
av_strerror(err, errorbuf, sizeof(errorbuf));
}
mutex.unlock();
if (pkt.size != 0)
{
int pts = pkt.pts * r2d(ic->streams[pkt.stream_index]->time_base) * 1000;
printf("pts = %d \n", pts);
}
return pkt;
}
AVFrame *XFFmpeg::Decode(const AVPacket *pkt)
{
mutex.lock();
if (!ic)
{
mutex.unlock();
return NULL;
}
if (yuv == NULL)
{
yuv = av_frame_alloc();
}
int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);
if (re != 0)
{
mutex.unlock();
return NULL;
}
re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, yuv);
if (re != 0)
{
mutex.unlock();
return NULL;
}
mutex.unlock();
在解码一帧后,获取yuv的显示时间戳pts
pts = (yuv->pts * r2d(ic->streams[pkt->stream_index]->time_base)) * 1000; //得到毫秒数
return yuv;
}
拖动进度条,将拖拽点的位置按照比例换算成显示时间戳pts,清空视频缓存.
bool XFFmpeg::Seek(float pos)
{
mutex.lock();
if (!ic)
{
mutex.unlock();
return false;
}
int64_t stamp = 0; //存放时间戳
stamp = pos * ic->streams[videoStream]->duration; //实际要seek的位置
int re = av_seek_frame(ic,
videoStream,
stamp,
AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_FRAME); //关键帧
avcodec_flush_buffers(ic->streams[videoStream]->codec); //释放刚刚解码完的数据(seek之前)
pts = stamp * r2d(ic->streams[videoStream]->time_base) * 1000; //得到毫秒数
mutex.unlock();
if (re > 0)
return false;
return true;
}
bool XFFmpeg::ToGRB(char *out, int outwidth, int outheight)
{
mutex.lock();
if (!ic || !yuv)
{
mutex.unlock();
return NULL;
}
AVCodecContext *videoCtx = ic->streams[this->videoStream]->codec;
cCtx = sws_getCachedContext(cCtx, videoCtx->width,
videoCtx->height,
videoCtx->pix_fmt, //源参数 解码之后视频帧, 存的是什么格式, 现在我们还没有解码 (1.可以直接写死, 但通用性低. 2.解码一次以上再调用这段代码)
outwidth,
outheight,
AV_PIX_FMT_BGRA,
SWS_BICUBIC, //转码用什么算法
NULL, NULL, NULL
);
if (!cCtx)
{
mutex.unlock();
//printf("sws_gegCachedContext success!\n");
printf("sws_gegCachedContext failed!\n");
return false;
}
uint8_t *data[AV_NUM_DATA_POINTERS] = { 0 };
data[0] = (uint8_t *)out;
int linesize[AV_NUM_DATA_POINTERS] = { 0 };
linesize[0] = outwidth * 4; //一行的大小, 宽 * 4
int h = sws_scale(cCtx, yuv->data, yuv->linesize, 0, videoCtx->height, //返回转码后的高度
data,
linesize);
if (h > 0)
{
printf("(h:%d) ", h);
}
mutex.unlock();
return true;
}
XFFmpeg::XFFmpeg()
{
errorbuf[0] = '\0';
av_register_all();
}
XFFmpeg::~XFFmpeg()
{
}
绘制画面
重写QOpenGLWidget的draw方法,利用GPU定时刷新界面.
#include "VideoWidget.h"
#include
#include "XFFmpeg.h"
#include "XVideoThread.h"
VideoWidget::VideoWidget(QWidget *p) : QOpenGLWidget(p)
{
startTimer(20);
XVideoThread::Get()->start();
}
VideoWidget::~VideoWidget()
{
}
void VideoWidget::paintEvent(QPaintEvent *e)
{
static QImage *image = NULL;
static int w = 0;
static int h = 0;
缩放
记录上一次的宽高,对比宽高. 若宽高改变,丢弃画面,再绘制画面。
if (w != width() || h != height())
{
if (image)
{
delete image->bits();
delete image;
image = NULL;
}
}
if (image == NULL)
{
uchar *buf = new uchar[width() * height() * 4];
image = new QImage(buf, width(), height(), QImage::Format_ARGB32);
}
XFFmpeg::Get()->ToGRB((char*)image->bits(), width(), height());
QPainter painter;
painter.begin(this);
painter.drawImage(QPoint(0, 0), *image);
painter.end();
}
void VideoWidget::timerEvent(QTimerEvent *e)
{
this->update();
}
#include "czyplayer.h"
#include
#include "XFFmpeg.h"
#include
static bool isPressSlider = false;
static bool isPlay = true;
显示播放时间
CzyPlayer::CzyPlayer(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
startTimer(100);
}
void CzyPlayer::timerEvent(QTimerEvent *e)
{
int min = (XFFmpeg::Get()->pts / 1000) / 60;
int sec = (XFFmpeg::Get()->pts / 1000) % 60;
char buf[1024] = { 0 };
sprintf(buf, "%02d:%02d", min, sec);
ui.playtime->setText(buf);
if (XFFmpeg::Get()->totalMs > 0)
{
float rate = (float)XFFmpeg::Get()->pts / (float)XFFmpeg::Get()->totalMs;
if (!isPressSlider) //按下的时候不显示
ui.playSlider->setValue(rate * 1000);
}
}
void CzyPlayer::resizeEvent(QResizeEvent *e)
{
ui.openGLWidget->resize(size());
ui.playButton->move(10, this->height() - 46);
ui.openButton->move(this->width() - 60, this->height() - 48);
ui.playtime->move(64, this->height() - 40);
ui.totaltime->move(this->width() - 110, this->height() - 40);
ui.playSlider->move(114, this->height() - 34);
ui.playSlider->resize(this->width() - 240, ui.playSlider->height());
}
拖动进度条
void CzyPlayer::sliderPress()
{
isPressSlider = true;
}
void CzyPlayer::sliderRelease()
{
isPressSlider = false;
float pos = 0;
pos = (float)ui.playSlider->value() / (float)(ui.playSlider->maximum() + 1);
XFFmpeg::Get()->Seek(pos);
}
播放、暂停切换
void CzyPlayer::play()
{
//border-image: url(:/CzyPlayer/Resources/play_normal.png);
isPlay = !isPlay;
XFFmpeg::Get()->isPlay = isPlay;
if (isPlay)
{
ui.playButton->setStyleSheet("border-image: url(:/CzyPlayer/Resources/pause_normal.png)");
}
else
{
ui.playButton->setStyleSheet("border-image: url(:/CzyPlayer/Resources/play_normal.png)");
}
}
void CzyPlayer::open()
{
QString name = QFileDialog::getOpenFileName(
this,
QString::fromLocal8Bit("选择视频文件"));
if (name.isEmpty())
{
return;
}
this->setWindowTitle(name);
int totalMs = XFFmpeg::Get()->Open(name.toLocal8Bit());
if (totalMs <=0)
{
QMessageBox::information(this, "err", "file open failed!");
return;
}
int min = (totalMs / 1000) / 60;
int sec = (totalMs / 1000) % 60;
char buf[1024] = { 0 };
sprintf(buf, "%02d:%02d", min, sec);
ui.totaltime->setText(buf);
isPlay = false;
play();
}
CzyPlayer::~CzyPlayer()
{
}
程序运行结果
源码下载 密码:6hsp