介绍
工作开发中需要处理的文件很多并无音频,针对这一场景,这里分享工作中自己封装使用的类库。精简的代码实现了播放、暂停、停止、快进、快退、进度更新跳转播放功能。直接放代码,方便后期复制使用。
代码
头文件
cpp
#ifndef VIDEOPLAYER_H
#define VIDEOPLAYER_H
#include <QThread>
#include <QDebug>
#include <QImage>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
}
class VideoDecoder : public QThread
{
Q_OBJECT
public:
explicit VideoDecoder(QObject *parent = nullptr);
~VideoDecoder();
void startPlay(const QString &path);
void stopPlay();
void pausePlay(bool pause);
void seekPlay(int sec);
signals:
void sigDuration(int sec);
void sigPlayPosition(int sec);
void sigPlayFinished(int ret);
void sigSendImage(const QImage &image);
protected:
void run();
private:
//运行标志
volatile bool m_isRun = false;
//暂停状态
volatile bool m_pause = false;
//进度跳转状态
volatile bool m_seek = false;
//优化跳转速度
volatile bool m_seekFilter = false;
//开始时间 ms单位
int64_t m_startTime;
//暂停时间 ms单位
int64_t m_pauseTime;
//跳转时间 ms单位
int64_t m_seekTime;
//文件路径
QString m_filePath;
//时长信息 秒
int m_videoDuration;
};
#endif // VIDEOPLAYER_H
实现文件
cpp
#include "videoplayer.h"
VideoDecoder::VideoDecoder(QObject *parent) : QThread(parent)
{
}
VideoDecoder::~VideoDecoder()
{
quit();
wait();
}
void VideoDecoder::startPlay(const QString &path)
{
m_filePath = path;
m_isRun = true;
m_pause = false;
m_seek = false;
this->start();
}
void VideoDecoder::stopPlay()
{
m_pause = false;
m_seek = false;
m_isRun = false;
}
void VideoDecoder::pausePlay(bool pause)
{
m_pause = pause;
if(pause)
{
m_pauseTime = av_gettime_relative() / 1000;
}
else
{
int offset = av_gettime_relative() / 1000 - m_pauseTime;
m_startTime += offset;
}
}
void VideoDecoder::seekPlay(int sec)
{
if(!m_isRun)
return;
if(m_videoDuration == sec)
sec -= 2;
m_seekTime = sec * 1000;
m_seekFilter = true;
m_seek = true;
}
void VideoDecoder::run()
{
qDebug() << "VideoDecoder start" << m_filePath;
std::string temp = m_filePath.toStdString();
AVFormatContext *inFmtCtx = avformat_alloc_context();
int ret = avformat_open_input(&inFmtCtx, temp.c_str(), NULL, NULL);
if (ret < 0)
{
qDebug() << "open input error";
return;
}
//获取流信息
ret = avformat_find_stream_info(inFmtCtx, NULL);
if (ret < 0)
{
qDebug() << "find stream info error";
return;
}
//获取视频流信息 目前只有视频流
bool getVideo = false;
int videoIndex = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVStream *videoStream = NULL;
AVCodec *videoDecoder = NULL;
AVCodecContext *videoDeCodecCtx = NULL;
if (videoIndex >= 0)
{
videoStream = inFmtCtx->streams[videoIndex];
//初始化解码器
videoDecoder = avcodec_find_decoder(videoStream->codecpar->codec_id);
videoDeCodecCtx = avcodec_alloc_context3(videoDecoder);
if(videoDeCodecCtx != NULL)
{
avcodec_parameters_to_context(videoDeCodecCtx, videoStream->codecpar);
ret = avcodec_open2(videoDeCodecCtx, videoDecoder, NULL);
if(ret < 0)
avcodec_free_context(&videoDeCodecCtx);
else
getVideo = true;
}
}
if(!getVideo)
{
avformat_close_input(&inFmtCtx);
return;
}
AVFrame *swsFrame = av_frame_alloc();
SwsContext *swsCtx = nullptr;
uint8_t *videoData = nullptr;
//输出视频参数信息
if(getVideo)
{
int srcW = videoStream->codecpar->width;
int srcH = videoStream->codecpar->height;
AVPixelFormat format = videoDeCodecCtx->pix_fmt;
m_videoDuration = inFmtCtx->duration / AV_TIME_BASE;
emit sigDuration(m_videoDuration);
int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, srcW, srcH, 1);
videoData = (uint8_t *)av_malloc(byte * sizeof(uint8_t));
av_image_fill_arrays(swsFrame->data, swsFrame->linesize, videoData, (AVPixelFormat)AV_PIX_FMT_RGB32, srcW, srcH, 1);
swsCtx = sws_getContext(srcW, srcH, (AVPixelFormat)format, srcW, srcH, (AVPixelFormat)AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);
}
//开始时刻
m_startTime = av_gettime_relative() / 1000;
int64_t ptsTime = 0;
int curPlayPos = 0;
AVPacket *packet = av_packet_alloc();
AVFrame *videoFrame = av_frame_alloc();
while(m_isRun)
{
//暂停
if(m_pause)
{
QThread::msleep(200);
continue;
}
//进度切换
if(m_seek)
{
//跳转的播放时刻 单位微秒
int64_t timeStamp = m_seekTime * 1000;
if (inFmtCtx->start_time != AV_NOPTS_VALUE)
timeStamp += inFmtCtx->start_time;
//注:seek若关键帧间隔大需避免延时
timeStamp = av_rescale_q(timeStamp, AVRational{1, AV_TIME_BASE}, videoStream->time_base);
ret = av_seek_frame(inFmtCtx, videoIndex, timeStamp, AVSEEK_FLAG_BACKWARD);
if(ret < 0)
{
qDebug() << "av_seek_frame fail" << m_seekTime;
}
else
{
//清空内部帧队列
if(videoDeCodecCtx)
avcodec_flush_buffers(videoDeCodecCtx);
//调整时钟
int64_t offset = m_seekTime - ptsTime;
m_startTime -= offset;
}
m_seek = false;
}
//不断读取packet
ret = av_read_frame(inFmtCtx, packet);
if (ret == AVERROR_EOF)
{
m_isRun = false;
break;
}
if(packet->stream_index == videoIndex)
{
//编码数据进行解码
ret = avcodec_send_packet(videoDeCodecCtx, packet);
if (ret < 0)
{
av_packet_unref(packet);
continue;
}
ret = avcodec_receive_frame(videoDeCodecCtx, videoFrame);
if (ret < 0)
{
av_packet_unref(packet);
continue;
}
//控制速度 ms单位
ptsTime = videoFrame->pts * av_q2d(videoStream->time_base) * 1000;
if(m_seekFilter)
{
//跳转播放时间不符合的帧直接丢弃 默认阈值200ms
int offset = m_seekTime - ptsTime;
if(0 > offset || offset < 200)
{
m_seekFilter = false;
}
else
{
av_frame_unref(videoFrame);
av_packet_unref(packet);
continue;
}
}
qint64 elapsed = av_gettime_relative() / 1000 - m_startTime;
int64_t sleepMs = ptsTime - elapsed;
if(sleepMs > 3)
{
QThread::msleep(sleepMs);
}
//发送播放位置信息
int sec = ptsTime / 1000;
if(sec != curPlayPos)
{
curPlayPos = sec;
emit sigPlayPosition(curPlayPos);
}
//将解码后的frame数据转换为Image
sws_scale(swsCtx, (const uint8_t *const *)videoFrame->data, videoFrame->linesize, 0, videoFrame->height, swsFrame->data, swsFrame->linesize);
QImage image((uchar *)videoData, videoFrame->width, videoFrame->height, QImage::Format_RGB32);
QImage copy = image.copy();
emit sigSendImage(copy);
av_frame_unref(videoFrame);
}
av_packet_unref(packet);
}
//释放资源
sws_freeContext(swsCtx);
av_frame_free(&swsFrame);
av_free(videoData);
av_packet_free(&packet);
av_frame_free(&videoFrame);
avcodec_free_context(&videoDeCodecCtx);
avformat_close_input(&inFmtCtx);
emit sigPlayFinished(0);
qDebug() << "VideoDecoder end" << m_filePath;
return;
}