在视频监控行业经常看到两个厂家广告打得比较厉害,一个是青犀视频对应easyplayer,一个是大牛直播,两个最初都是sdk免费,并提供调用示例源码,后面大牛直播的sdk以及示例都无法运行,目前就剩下免费的easyplayer可以用,亲测下来确实免费可用不需要授权秘钥之类的,功能还行,支持各种音视频文件、本地摄像头、网络视频流等,就是在录制H265视频的时候不行,直接崩溃,估计官方放出的版本不支持,内部的版本肯定是支持的。
easyplayer的设计总体上估计参照了mpv播放器的设计,所有属性做成了可读取和设置,通过调用EasyPlayerPro_Setparam设置属性,调用EasyPlayerPro_Getparam获取属性,参数传入对应枚举值EASY_PARAM_ID即可,至于有哪些参数可以通过头文件枚举值查看,所有的参数可读取,不是所有的参数可设置。这种设计很巧妙和万能通用,不少优秀的库也都参照这种设计思路,这样后期增加功能只需要增加对应的枚举值即可。比如获取倍速调用 EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_PLAY_SPEED, &speed); 设置倍速调用 EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_PLAY_SPEED, &value); 看起来是不是很简洁通俗易懂。
#include "easyplayerthread.h"
#include "videohelper.h"
EasyPlayerThread::EasyPlayerThread(QObject *parent) : VideoThread(parent)
{
easyPlayer = NULL;
}
void EasyPlayerThread::setGeometry()
{
if (!easyPlayer) {
return;
}
//设置波形频谱可视化效果
int mode = 2;
EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_VISUAL_EFFECT, &mode);
//获取视频流编号
int videoId = -1;
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_VIDEO_STREAM_CUR, &videoId);
//获取音频流编号
int audioId = -1;
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_AUDIO_STREAM_CUR, &audioId);
int width = hwndWidget->width();
int height = hwndWidget->height();
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_VISUAL_EFFECT, &mode);
QString text = "空白";
if (mode == 1) {
text = "波形";
} else if (mode == 2) {
text = "频谱";
}
debug("设置信息", QString("效果: %1 视频: %2 音频: %3").arg(text).arg(videoId).arg(audioId));
//设置了不显示可视化效果/无视频/无音频则不显示波形频谱
if (mode == EASY_AUDIO_VISUAL_EFFECT_DISABLE || videoId < 0 || audioId < 0) {
EasyPlayerPro_Resize(easyPlayer, 0, 0, 0, width, height);
EasyPlayerPro_Resize(easyPlayer, 1, 0, 0, width, height);
} else {
int percent = 10;
EasyPlayerPro_Resize(easyPlayer, 0, 0, 0, width, height * (percent - 1) / percent);
EasyPlayerPro_Resize(easyPlayer, 1, 0, height * (percent - 1) / percent, width, height / percent);
}
}
bool EasyPlayerThread::openVideo()
{
//先检查地址是否正常(文件是否存在或者网络地址是否可达)
if (!VideoHelper::checkUrl(this, videoType, videoUrl, connectTimeout)) {
return false;
}
//启动计时
timer.start();
//安装事件过滤器识别尺寸变化
if (videoMode == VideoMode_Hwnd) {
hwndWidget->installEventFilter(this);
}
//创建实例
if (!easyPlayer) {
easyPlayer = EasyPlayerPro_Create();
}
//创建失败则返回
if (!easyPlayer) {
return false;
}
//准备参数
QByteArray urlData = VideoHelper::getRightUrl(videoType, videoUrl).toUtf8();
char *url = urlData.data();
HWND wid = (HWND)hwndWidget->winId();
EASY_STREAM_LINK_MODE linkMode = (transport == "tcp" ? EASY_STREAM_LINK_TCP : EASY_STREAM_LINK_UDP);
//通过句柄的方式设置播放
#ifdef easyplayerx
easyPlayer = EasyPlayerPro_Open(easyPlayer, url, wid, EASY_VIDEO_RENDER_TYPE_GDI, EASY_VIDEO_MODE_STRETCHED, linkMode, 100, 0);
#else
QString key = "EasyPlayer is Fr1ee!";
int ret = EasyPlayerPro_Authorize(key.toUtf8().data());
debug("剩余天数", QString("天数: %1").arg(ret));
int param = 1;
EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_VDEV_RENDER_SHOW, ¶m);
easyPlayer = EasyPlayerPro_Open(easyPlayer, url, wid, EASY_VIDEO_RENDER_TYPE_GDI, EASY_VIDEO_MODE_STRETCHED, linkMode, 100, 0, 1024 * 1024, 10000000);
#endif
isOk = true;
emit recorderStateChanged(RecorderState_Stopped, fileName);
lastTime = QDateTime::currentDateTime();
int time = timer.elapsed();
debug("打开成功", QString("用时: %1 毫秒").arg(time));
//只有获取到了宽高信息才算真正打开完成
//emit receivePlayStart(time);
return isOk;
}
void EasyPlayerThread::closeVideo()
{
//先停止录制
recordStop();
//搞个标志位判断是否需要调用父类的释放(可以防止重复调用)
bool needClose = (easyPlayer);
//释放对象
if (easyPlayer) {
EasyPlayerPro_Close(easyPlayer);
EasyPlayerPro_Release(easyPlayer);
easyPlayer = NULL;
}
if (needClose) {
VideoThread::closeVideo();
}
}
void EasyPlayerThread::readMediaInfo()
{
if (!easyPlayer) {
return;
}
#if 0
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_VIDEO_WIDTH, &videoWidth);
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_VIDEO_HEIGHT, &videoHeight);
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_VDEV_FRAME_RATE, &frameRate);
#else
MediaInfo mediaInfo;
memset(&mediaInfo, 0x00, sizeof(MediaInfo));
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_MEDIA_INFO, &mediaInfo);
videoWidth = mediaInfo.nWidth;
videoHeight = mediaInfo.nHeight;
frameRate = mediaInfo.nFrameRate;
#endif
//纯音频则取父窗体的尺寸
if (onlyAudio) {
QWidget *widget = (QWidget *)hwndWidget->parent();
videoWidth = widget->width();
videoHeight = widget->height();
}
if (videoWidth > 0 && videoHeight > 0) {
emit receivePlayStart(timer.elapsed());
QString msg = QString("宽高: %1x%2 帧率: %3 角度: %4").arg(videoWidth).arg(videoHeight).arg(frameRate).arg(rotate);
debug("媒体信息", msg);
this->setGeometry();
emit receiveVolume(getVolume());
}
}
qint64 EasyPlayerThread::getDuration()
{
//没有获取过才需要获取
if (easyPlayer && duration == 0 && getIsFile()) {
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_MEDIA_DURATION, &duration);
emit receiveDuration(duration);
}
return duration;
}
qint64 EasyPlayerThread::getPosition()
{
qint64 position = 0;
if (easyPlayer) {
this->getDuration();
lastTime = QDateTime::currentDateTime();
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_MEDIA_POSITION, &position);
emit receivePosition(position);
//如果设置了重复循环播放则快到了文件末尾重新设置位置即可
int offset = duration - position;
if (duration > 0 && offset < 500) {
if (this->getPlayRepeat()) {
QMetaObject::invokeMethod(this, "setPosition", Q_ARG(qint64, 0));
} else {
this->stop2();
}
}
}
return position;
}
void EasyPlayerThread::setPosition(qint64 position)
{
//文件才能指定播放位置(保存文件阶段不允许切换进度否则录制的文件错乱)
if (easyPlayer && getIsFile() && !isRecord) {
EasyPlayerPro_Seek(easyPlayer, position);
}
}
double EasyPlayerThread::getSpeed()
{
double speed = 1.0;
if (easyPlayer) {
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_PLAY_SPEED, &speed);
speed = speed / 100;
}
return speed;
}
void EasyPlayerThread::setSpeed(double speed)
{
//文件才能指定播放速度
if (easyPlayer && getIsFile()) {
int value = speed * 100;
EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_PLAY_SPEED, &value);
}
}
int EasyPlayerThread::getVolume()
{
//声音范围是[-182, 73] 需要转换成[0, 100]
int volume = 0;
if (easyPlayer) {
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_AUDIO_VOLUME, &volume);
volume = VideoHelper::getRangeValue(-182, 73, volume, 0, 100);
}
return volume;
}
void EasyPlayerThread::setVolume(int volume)
{
emit receiveVolume(volume);
if (easyPlayer) {
volume = VideoHelper::getRangeValue(0, 100, volume, -182, 73);
EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_AUDIO_VOLUME, &volume);
}
}
bool EasyPlayerThread::getMuted()
{
int muted = 0;
if (easyPlayer) {
EasyPlayerPro_Getparam(easyPlayer, EASY_PARAM_ADEV_MUTE, &muted);
}
return (muted == 1);
}
void EasyPlayerThread::setMuted(bool muted)
{
emit receiveMuted(muted);
if (easyPlayer) {
int value = muted ? 1 : 0;
EasyPlayerPro_Setparam(easyPlayer, EASY_PARAM_ADEV_MUTE, &value);
}
}
void EasyPlayerThread::pause()
{
if (easyPlayer && !isPause) {
isPause = true;
EasyPlayerPro_Pause(easyPlayer);
}
}
void EasyPlayerThread::next()
{
if (easyPlayer && isPause) {
isPause = false;
EasyPlayerPro_Play(easyPlayer);
}
}
void EasyPlayerThread::snap(const QString &snapName)
{
this->setSnapName(snapName);
if (easyPlayer) {
int result = EasyPlayerPro_Snapshot(easyPlayer, snapName.toUtf8().data(), 0, 0, 200);
if (result >= 0) {
QMetaObject::invokeMethod(this, "snapFinsh");
}
}
}
void EasyPlayerThread::recordStart(const QString &fileName)
{
this->setFileName(fileName);
if (easyPlayer && !isRecord) {
int result = EasyPlayerPro_Record(easyPlayer, fileName.toUtf8().data(), 0);
if (result >= 0) {
isRecord = true;
emit recorderStateChanged(RecorderState_Recording, fileName);
debug("开始录制", QString("文件: %1").arg(fileName));
}
}
}
void EasyPlayerThread::recordStop()
{
if (easyPlayer && isRecord) {
int result = EasyPlayerPro_Stoprecord(easyPlayer);
if (result >= 0) {
isRecord = false;
emit recorderStateChanged(RecorderState_Stopped, fileName);
debug("结束录制", QString("文件: %1").arg(fileName));
}
}
}