ui修改
61
CMakeLists.txt
Normal file
@@ -0,0 +1,61 @@
|
||||
cmake_minimum_required(VERSION 3.19)
|
||||
project(gdmp LANGUAGES CXX)
|
||||
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core Widgets)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/log
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party/FFmpeg/include
|
||||
)
|
||||
file(GLOB EASYLOG_SOURCES "log/*.cc" "log/*.h")
|
||||
file(GLOB UI_SOURCES "*.ui")
|
||||
file(GLOB SOURCES "*.cpp" "*.h")
|
||||
|
||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/third_party/FFmpeg/lib)
|
||||
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
qt_add_executable(gdmp
|
||||
WIN32 MACOSX_BUNDLE
|
||||
${EASYLOG_SOURCES}
|
||||
${SOURCES}
|
||||
${UI_SOURCES}
|
||||
resource.qrc
|
||||
)
|
||||
|
||||
qt6_add_resources(RESOURCE_FILES resource.qrc)
|
||||
|
||||
target_sources(gdmp PRIVATE ${RESOURCE_FILES})
|
||||
|
||||
target_link_libraries(gdmp
|
||||
PRIVATE
|
||||
Qt::Core
|
||||
Qt::Widgets
|
||||
avformat
|
||||
avcodec
|
||||
avdevice
|
||||
avfilter
|
||||
avutil
|
||||
swresample
|
||||
swscale
|
||||
SDL2::SDL2
|
||||
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
install(TARGETS gdmp
|
||||
BUNDLE DESTINATION .
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
qt_generate_deploy_app_script(
|
||||
TARGET gdmp
|
||||
OUTPUT_SCRIPT deploy_script
|
||||
NO_UNSUPPORTED_PLATFORM_ERROR
|
||||
)
|
||||
install(SCRIPT ${deploy_script})
|
||||
44
customslider.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "customslider.h"
|
||||
#include "globalhelper.h"
|
||||
|
||||
CustomSlider::CustomSlider(QWidget *parent)
|
||||
: QSlider(parent)
|
||||
{
|
||||
this->setMaximum(MAX_SLIDER_VALUE);
|
||||
}
|
||||
|
||||
CustomSlider::~CustomSlider()
|
||||
{
|
||||
}
|
||||
|
||||
void CustomSlider::mousePressEvent(QMouseEvent *ev)
|
||||
{
|
||||
bIsPressed = true;
|
||||
//注意应先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况
|
||||
QSlider::mousePressEvent(ev);
|
||||
//获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
|
||||
double pos = ev->pos().x() / (double)width();
|
||||
setValue(pos * (maximum() - minimum()) + minimum());
|
||||
|
||||
emit SigCustomSliderValueChanged(this->value());
|
||||
}
|
||||
|
||||
void CustomSlider::mouseReleaseEvent(QMouseEvent *ev)
|
||||
{
|
||||
bIsPressed = false;
|
||||
QSlider::mouseReleaseEvent(ev);
|
||||
|
||||
//emit SigCustomSliderValueChanged();
|
||||
}
|
||||
|
||||
void CustomSlider::mouseMoveEvent(QMouseEvent *ev)
|
||||
{
|
||||
if (!bIsPressed)
|
||||
return;
|
||||
QSlider::mouseMoveEvent(ev);
|
||||
//获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
|
||||
double pos = ev->pos().x() / (double)width();
|
||||
setValue(pos * (maximum() - minimum()) + minimum());
|
||||
|
||||
emit SigCustomSliderValueChanged(this->value());
|
||||
}
|
||||
22
customslider.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSlider>
|
||||
#include <QMouseEvent>
|
||||
|
||||
class CustomSlider : public QSlider
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CustomSlider(QWidget *parent);
|
||||
~CustomSlider();
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *ev);//重写QSlider的mousePressEvent事件
|
||||
void mouseReleaseEvent(QMouseEvent *ev);
|
||||
void mouseMoveEvent(QMouseEvent *ev);
|
||||
signals:
|
||||
void SigCustomSliderValueChanged(int value);//自定义的鼠标单击信号,用于捕获并处理
|
||||
|
||||
private:
|
||||
bool bIsPressed = false;
|
||||
};
|
||||
215
displaywind.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include "displaywind.h"
|
||||
#include "ui_displaywind.h"
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
DisplayWind::DisplayWind(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::DisplayWind),
|
||||
bIsFull_(false)
|
||||
// stParentWidget_(parent)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
win_width_ = width();
|
||||
win_height_ = height();
|
||||
memset(&dst_video_frame_, sizeof(VideoFrame), 0);
|
||||
play_state_ = 2;
|
||||
}
|
||||
|
||||
DisplayWind::~DisplayWind()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
delete ui;
|
||||
DeInit();
|
||||
}
|
||||
|
||||
int DisplayWind::Draw(const Frame *frame)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if(!img_scaler_ || req_resize_) {
|
||||
if(img_scaler_) {
|
||||
DeInit();
|
||||
}
|
||||
win_width_ = width();
|
||||
win_height_ = height();
|
||||
video_width = frame->width;
|
||||
video_height = frame->height;
|
||||
img_scaler_ = new ImageScaler();
|
||||
double video_aspect_ratio = frame->width * 1.0 / frame->height;
|
||||
double win_aspect_ratio = win_width_ * 1.0 / win_height_;
|
||||
if(win_aspect_ratio > video_aspect_ratio) {
|
||||
//此时应该是调整x的起始位置,以高度为基准
|
||||
img_height = win_height_;
|
||||
img_height &= 0xfffc;
|
||||
img_width = img_height * video_aspect_ratio;
|
||||
img_width &= 0xfffc;
|
||||
y_ = 0;
|
||||
x_ = (win_width_ - img_width) / 2;
|
||||
} else {
|
||||
//此时应该是调整y的起始位置,以宽度为基准
|
||||
img_width = win_width_;
|
||||
img_width &= 0xfffc;
|
||||
img_height = img_width / video_aspect_ratio;
|
||||
img_height &= 0xfffc;
|
||||
x_ = 0;
|
||||
y_ = (win_height_ - img_height) / 2;
|
||||
}
|
||||
img_scaler_->Init(video_width, video_height, frame->format,
|
||||
img_width, img_height, AV_PIX_FMT_RGB24);
|
||||
memset(&dst_video_frame_, 0, sizeof(VideoFrame));
|
||||
dst_video_frame_.width = img_width;
|
||||
dst_video_frame_.height = img_height;
|
||||
dst_video_frame_.format = AV_PIX_FMT_RGB24;
|
||||
dst_video_frame_.data[0] = (uint8_t*)malloc(img_width * img_height * 3);
|
||||
dst_video_frame_.linesize[0] = img_width * 3; // 每行的字节数
|
||||
req_resize_ = false;
|
||||
}
|
||||
img_scaler_->Scale3(frame, &dst_video_frame_);
|
||||
QImage imageTmp = QImage((uint8_t *)dst_video_frame_.data[0],
|
||||
img_width, img_height, QImage::Format_RGB888);
|
||||
img = imageTmp.copy(0, 0, img_width, img_height);
|
||||
update();
|
||||
// repaint();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void DisplayWind::DeInit()
|
||||
{
|
||||
if(dst_video_frame_.data[0]) {
|
||||
free(dst_video_frame_.data[0]);
|
||||
dst_video_frame_.data[0] = NULL;
|
||||
}
|
||||
if(img_scaler_) {
|
||||
delete img_scaler_;
|
||||
img_scaler_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWind::StartPlay()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
play_state_ = 1;
|
||||
}
|
||||
|
||||
void DisplayWind::StopPlay()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
play_state_ = 2;
|
||||
update();
|
||||
}
|
||||
|
||||
void DisplayWind::onToggleFullScreen(bool full)
|
||||
{
|
||||
if (full && !bIsFull_) {
|
||||
enterFullScreen();
|
||||
}
|
||||
else if (!full && bIsFull_) {
|
||||
exitFullScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWind::enterFullScreen()
|
||||
{
|
||||
if (bIsFull_)
|
||||
return;
|
||||
|
||||
bIsFull_ = true;
|
||||
nWinHeightBack_ = height();
|
||||
nWinWidthBack_ = width();
|
||||
stParentWidget_ = this->parentWidget();
|
||||
|
||||
if (stParentWidget_) {
|
||||
stParentLayout_ = qobject_cast<QBoxLayout*>(stParentWidget_->layout());
|
||||
if (stParentLayout_) {
|
||||
nParentLayoutIndex_ = stParentLayout_->indexOf(this);
|
||||
stParentLayout_->removeWidget(this);
|
||||
}
|
||||
else {
|
||||
stOriginalGeometry_ = geometry();
|
||||
}
|
||||
setParent(nullptr);
|
||||
}
|
||||
setWindowFlags(Qt::Window);
|
||||
showFullScreen();
|
||||
|
||||
setFocus();
|
||||
|
||||
emit signalFullScreenChanged();
|
||||
}
|
||||
|
||||
void DisplayWind::exitFullScreen()
|
||||
{
|
||||
|
||||
if (!bIsFull_)
|
||||
return;
|
||||
|
||||
bIsFull_ = false;
|
||||
setWindowFlags(Qt::Widget);
|
||||
hide();
|
||||
|
||||
if (stParentWidget_) {
|
||||
setParent(stParentWidget_);
|
||||
|
||||
if (stParentLayout_ && nParentLayoutIndex_ >= 0) {
|
||||
LOG(DEBUG)<< "insertWidget";
|
||||
stParentLayout_->insertWidget(nParentLayoutIndex_, this);
|
||||
}
|
||||
else if (!stOriginalGeometry_.isNull()) {
|
||||
LOG(DEBUG)<< "setGeometry";
|
||||
setGeometry(stOriginalGeometry_);
|
||||
}
|
||||
}
|
||||
resize(nWinWidthBack_, nWinHeightBack_);
|
||||
show();
|
||||
setFocus();
|
||||
stParentLayout_ = nullptr;
|
||||
nParentLayoutIndex_ = 0;
|
||||
stOriginalGeometry_ = QRect();
|
||||
|
||||
emit signalFullScreenChanged();
|
||||
}
|
||||
|
||||
|
||||
void DisplayWind::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if(play_state_ == 1) { // 播放状态
|
||||
if (img.isNull()) {
|
||||
return;
|
||||
}
|
||||
QPainter painter(this);
|
||||
painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
||||
// // p.translate(X, Y);
|
||||
// // p.drawImage(QRect(0, 0, W, H), img);
|
||||
QRect rect = QRect(x_, y_, img.width(), img.height());
|
||||
// qDebug() << rect << ", win_w:" << this->width() << ", h:" << this->height();
|
||||
painter.drawImage(rect, img.scaled(img.width(), img.height()));
|
||||
} else if(play_state_ == 2) {
|
||||
QPainter p(this);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(Qt::black);
|
||||
p.drawRect(rect());
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWind::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if(win_width_ != width() || win_height_ != height()) {
|
||||
// DeInit(); // 释放尺寸缩放资源,等下一次draw的时候重新初始化
|
||||
// win_width = width();
|
||||
// win_height = height();
|
||||
req_resize_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWind::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (bIsFull_) {
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
exitFullScreen();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
75
displaywind.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#ifndef DISPLAYWIND_H
|
||||
#define DISPLAYWIND_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMutex>
|
||||
#include "ijkmediaplayer.h"
|
||||
#include "imagescaler.h"
|
||||
#include <QKeyEvent>
|
||||
#include <QBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
|
||||
namespace Ui {
|
||||
class DisplayWind;
|
||||
}
|
||||
|
||||
class DisplayWind : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DisplayWind(QWidget *parent = 0);
|
||||
~DisplayWind();
|
||||
int Draw(const Frame *frame);
|
||||
void DeInit();
|
||||
void StartPlay();
|
||||
void StopPlay();
|
||||
|
||||
signals:
|
||||
void signalFullScreenChanged();
|
||||
|
||||
public slots:
|
||||
void onToggleFullScreen(bool full);
|
||||
protected:
|
||||
// 这里不要重载event事件,会导致paintEvent不被触发
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
private:
|
||||
void enterFullScreen();
|
||||
void exitFullScreen();
|
||||
private:
|
||||
Ui::DisplayWind *ui;
|
||||
|
||||
int m_nLastFrameWidth; ///< 记录视频宽高
|
||||
int m_nLastFrameHeight;
|
||||
bool is_display_size_change_ = false;
|
||||
|
||||
int x_ = 0; // 起始位置
|
||||
int y_ = 0;
|
||||
int video_width = 0;
|
||||
int video_height = 0;
|
||||
int img_width = 0;
|
||||
int img_height = 0;
|
||||
int win_width_ = 0;
|
||||
int win_height_ = 0;
|
||||
bool req_resize_ = false;
|
||||
QImage img;
|
||||
VideoFrame dst_video_frame_;
|
||||
QMutex m_mutex;
|
||||
ImageScaler *img_scaler_ = NULL;
|
||||
|
||||
// 全屏
|
||||
bool bIsFull_ = false;
|
||||
QWidget* stParentWidget_ = nullptr;
|
||||
QBoxLayout* stParentLayout_ = nullptr;
|
||||
int nParentLayoutIndex_ = 0;
|
||||
QRect stOriginalGeometry_;
|
||||
int nWinWidthBack_ = 0;
|
||||
int nWinHeightBack_ = 0;
|
||||
|
||||
int play_state_ = 0; // 0 初始化状态; 1 播放状态; 2 停止状态
|
||||
};
|
||||
|
||||
#endif // DISPLAYWIND_H
|
||||
20
displaywind.ui
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DisplayWind</class>
|
||||
<widget class="QWidget" name="DisplayWind">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>771</width>
|
||||
<height>471</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
7
ff_fferror.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef FF_FFERROR_H
|
||||
#define FF_FFERROR_H
|
||||
#define EIJK_FAILED -1
|
||||
#define EIJK_OUT_OF_MEMORY -2
|
||||
#define EIJK_INVALID_STATE -3
|
||||
#define EIJK_NULL_IS_PTR -4
|
||||
#endif // FF_FFERROR_H
|
||||
1542
ff_ffplay.cpp
Normal file
234
ff_ffplay.h
Normal file
@@ -0,0 +1,234 @@
|
||||
#ifndef FF_FFPLAY_H
|
||||
#define FF_FFPLAY_H
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include "ffmsg_queue.h"
|
||||
#include "ff_ffplay_def.h"
|
||||
#include "sonic.h"
|
||||
extern "C" {
|
||||
#include "libavcodec/avcodec.h"
|
||||
}
|
||||
class Decoder
|
||||
{
|
||||
public:
|
||||
int packet_pending_ = 0;
|
||||
AVPacket pkt_;
|
||||
PacketQueue *queue_; // 数据包队列
|
||||
AVCodecContext *avctx_; // 解码器上下文
|
||||
int pkt_serial_; // 包序列
|
||||
int finished_; // =0,解码器处于工作状态;=非0,解码器处于空闲状态
|
||||
std::thread *decoder_thread_ = NULL;
|
||||
|
||||
int64_t start_pts;
|
||||
AVRational start_pts_tb;
|
||||
int64_t next_pts;
|
||||
AVRational next_pts_tb;
|
||||
Decoder();
|
||||
~Decoder();
|
||||
void decoder_init(AVCodecContext *avctx, PacketQueue *queue);
|
||||
// 创建和启动线程
|
||||
int decoder_start(enum AVMediaType codec_type, const char *thread_name, void* arg);
|
||||
// 停止线程
|
||||
void decoder_abort(FrameQueue *fq);
|
||||
void decoder_destroy();
|
||||
int decoder_decode_frame(AVFrame *frame);
|
||||
int get_video_frame(AVFrame *frame);
|
||||
int queue_picture(FrameQueue *fq, AVFrame *src_frame, double pts,
|
||||
double duration, int64_t pos, int serial);
|
||||
int audio_thread(void* arg);
|
||||
int video_thread(void* arg);
|
||||
};
|
||||
class FFPlayer
|
||||
{
|
||||
public:
|
||||
FFPlayer();
|
||||
int ffp_create();
|
||||
void ffp_destroy();
|
||||
int ffp_prepare_async_l(char *file_name);
|
||||
|
||||
// 播放控制
|
||||
int ffp_start_l();
|
||||
int ffp_stop_l();
|
||||
int stream_open( const char *file_name);
|
||||
void stream_close();
|
||||
// 打开指定stream对应解码器、创建解码线程、以及初始化对应的输出
|
||||
int stream_component_open(int stream_index);
|
||||
// 关闭指定stream的解码线程,释放解码器资源
|
||||
void stream_component_close(int stream_index);
|
||||
|
||||
int audio_open(int64_t wanted_channel_layout,
|
||||
int wanted_nb_channels, int wanted_sample_rate,
|
||||
struct AudioParams *audio_hw_params);
|
||||
void audio_close();
|
||||
|
||||
//获取播放时长
|
||||
long ffp_get_duration_l();
|
||||
long ffp_get_current_position_l();
|
||||
|
||||
// 暂停恢复
|
||||
int ffp_pause_l();
|
||||
void toggle_pause(int pause_on);
|
||||
void toggle_pause_l(int pause_on);
|
||||
void stream_update_pause_l();
|
||||
void stream_toggle_pause_l(int pause_on);
|
||||
|
||||
// seek相关
|
||||
int ffp_seek_to_l(long msec);
|
||||
// 单位是秒 整数
|
||||
int ffp_forward_to_l(long incr);
|
||||
// 单位是秒 负数
|
||||
int ffp_back_to_l(long incr);
|
||||
int ffp_forward_or_back_to_l(long incr);
|
||||
void stream_seek(int64_t pos, int64_t rel, int seek_by_bytes);
|
||||
|
||||
// 截屏相关
|
||||
int ffp_screenshot_l(char *screen_path);
|
||||
void screenshot(AVFrame *frame);
|
||||
|
||||
// 变速相关
|
||||
int get_target_frequency();
|
||||
int get_target_channels();
|
||||
void ffp_set_playback_rate(float rate);
|
||||
float ffp_get_playback_rate();
|
||||
bool is_normal_playback_rate();
|
||||
int ffp_get_playback_rate_change();
|
||||
void ffp_set_playback_rate_change(int change);
|
||||
|
||||
//音量相关
|
||||
void ffp_set_playback_volume(int value);
|
||||
|
||||
//播放完毕相关判断 1. av_read_frame返回eof; 2. audio没有数据可以输出; 3.video没有数据可以输出
|
||||
void check_play_finish(); //如果已经结束则通知ui调用停止函数
|
||||
// 供外包获取信息
|
||||
int64_t ffp_get_property_int64(int id, int64_t default_value);
|
||||
void ffp_track_statistic_l(AVStream *st, PacketQueue *q, FFTrackCacheStatistic *cache);
|
||||
void ffp_audio_statistic_l();
|
||||
void ffp_video_statistic_l();
|
||||
MessageQueue msg_queue_;
|
||||
char *input_filename_;
|
||||
int realtime = 0;
|
||||
int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue);
|
||||
int read_thread();
|
||||
std::thread *read_thread_;
|
||||
|
||||
int video_refresh_thread();
|
||||
void video_refresh(double *remaining_time);
|
||||
double vp_duration( Frame *vp, Frame *nextvp);
|
||||
double compute_target_delay(double delay);
|
||||
void update_video_pts(double pts, int64_t pos, int serial);
|
||||
// 视频画面输出相关
|
||||
std::thread *video_refresh_thread_ = NULL;
|
||||
|
||||
std::function<int(const Frame *)> video_refresh_callback_ = NULL;
|
||||
void AddVideoRefreshCallback(std::function<int(const Frame *)> callback);
|
||||
|
||||
int get_master_sync_type();
|
||||
double get_master_clock();
|
||||
int av_sync_type = AV_SYNC_AUDIO_MASTER; // 音视频同步类型, 默认audio master
|
||||
Clock audclk; // 音频时钟
|
||||
Clock vidclk; // 视频时钟
|
||||
// Clock extclk;
|
||||
|
||||
double audio_clock = 0; // 当前音频帧的PTS+当前帧Duration
|
||||
int audio_clock_serial; // 播放序列,seek可改变此值, 解码后保存
|
||||
int64_t audio_callback_time = 0;
|
||||
// 帧队列
|
||||
FrameQueue pictq; // 视频Frame队列
|
||||
FrameQueue sampq; // 采样Frame队列
|
||||
|
||||
// 包队列
|
||||
PacketQueue audioq; // 音频packet队列
|
||||
PacketQueue videoq; // 视频队列
|
||||
int abort_request = 0;
|
||||
|
||||
AVStream *audio_st = NULL; // 音频流
|
||||
AVStream *video_st = NULL; // 音频流
|
||||
int force_refresh = 0;
|
||||
double frame_timer = 0;
|
||||
|
||||
int audio_stream = -1;
|
||||
int video_stream = -1;
|
||||
|
||||
Decoder auddec; // 音频解码器
|
||||
Decoder viddec; // 视频解码器
|
||||
|
||||
|
||||
int eof = 0;
|
||||
int audio_no_data = 0;
|
||||
int video_no_data = 0;
|
||||
AVFormatContext *ic = NULL;
|
||||
|
||||
int paused = 0;
|
||||
// 音频输出相关
|
||||
struct AudioParams audio_src; // 保存最新解码的音频参数
|
||||
struct AudioParams audio_tgt; // 保存SDL音频输出需要的参数
|
||||
struct SwrContext *swr_ctx = NULL; // 音频重采样context
|
||||
int audio_hw_buf_size = 0; // SDL音频缓冲区的大小(字节为单位)
|
||||
// 指向待播放的一帧音频数据,指向的数据区将被拷入SDL音频缓冲区。若经过重采样则指向audio_buf1,
|
||||
// 否则指向frame中的音频
|
||||
uint8_t *audio_buf = NULL; // 指向需要重采样的数据
|
||||
uint8_t *audio_buf1 = NULL; // 指向重采样后的数据
|
||||
unsigned int audio_buf_size = 0; // 待播放的一帧音频数据(audio_buf指向)的大小
|
||||
unsigned int audio_buf1_size = 0; // 申请到的音频缓冲区audio_buf1的实际尺寸
|
||||
int audio_buf_index = 0; // 更新拷贝位置 当前音频帧中已拷入SDL音频缓冲区
|
||||
int audio_write_buf_size;
|
||||
int audio_volume = 50; // 音量相关
|
||||
int startup_volume = 50; // 起始音量
|
||||
// seek相关
|
||||
int64_t seek_req = 0;
|
||||
int64_t seek_rel = 0;
|
||||
int64_t seek_flags = 0;
|
||||
int64_t seek_pos = 0; // seek的位置
|
||||
|
||||
// 截屏相关
|
||||
bool req_screenshot_ = false;
|
||||
char *screen_path_ = NULL;
|
||||
|
||||
//单步运行
|
||||
int step = 0;
|
||||
int framedrop = 1;
|
||||
int frame_drops_late = 0;
|
||||
|
||||
int pause_req = 0;
|
||||
int auto_resume = 0;
|
||||
int buffering_on = 0;
|
||||
// 变速相关
|
||||
float pf_playback_rate = 1.0; // 播放速率
|
||||
int pf_playback_rate_changed = 0; // 播放速率改变
|
||||
// 变速相关
|
||||
sonicStreamStruct *audio_speed_convert = nullptr;
|
||||
int max_frame_duration = 3600;
|
||||
|
||||
|
||||
// 统计相关的操作
|
||||
FFStatistic stat;
|
||||
};
|
||||
|
||||
inline static void ffp_notify_msg1(FFPlayer *ffp, int what)
|
||||
{
|
||||
msg_queue_put_simple3(&ffp->msg_queue_, what, 0, 0);
|
||||
}
|
||||
|
||||
inline static void ffp_notify_msg2(FFPlayer *ffp, int what, int arg1)
|
||||
{
|
||||
msg_queue_put_simple3(&ffp->msg_queue_, what, arg1, 0);
|
||||
}
|
||||
|
||||
inline static void ffp_notify_msg3(FFPlayer *ffp, int what, int arg1, int arg2)
|
||||
{
|
||||
msg_queue_put_simple3(&ffp->msg_queue_, what, arg1, arg2);
|
||||
}
|
||||
|
||||
inline static void ffp_notify_msg4(FFPlayer *ffp, int what, int arg1, int arg2, void *obj, int obj_len)
|
||||
{
|
||||
msg_queue_put_simple4(&ffp->msg_queue_, what, arg1, arg2, obj, obj_len);
|
||||
}
|
||||
|
||||
inline static void ffp_remove_msg(FFPlayer *ffp, int what)
|
||||
{
|
||||
msg_queue_remove(&ffp->msg_queue_, what);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif // FF_FFPLAY_H
|
||||
394
ff_ffplay_def.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
#include "ff_ffplay_def.h"
|
||||
#include "easylogging++.h"
|
||||
|
||||
AVPacket flush_pkt;
|
||||
static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
|
||||
{
|
||||
MyAVPacketList *pkt1;
|
||||
|
||||
if (q->abort_request) //如果已中止,则放入失败
|
||||
return -1;
|
||||
|
||||
pkt1 = (MyAVPacketList *)av_malloc(sizeof(MyAVPacketList)); //分配节点内存
|
||||
if (!pkt1) //内存不足,则放入失败
|
||||
return -1;
|
||||
// 没有做引用计数,那这里也说明av_read_frame不会释放替用户释放buffer。
|
||||
pkt1->pkt = *pkt; //拷贝AVPacket(浅拷贝,AVPacket.data等内存并没有拷贝)
|
||||
pkt1->next = NULL;
|
||||
if (pkt == &flush_pkt)//如果放入的是flush_pkt,需要增加队列的播放序列号,以区分不连续的两段数据
|
||||
{
|
||||
q->serial++;
|
||||
LOG(INFO) << "q->serial = " << q->serial;
|
||||
}
|
||||
pkt1->serial = q->serial; //用队列序列号标记节点
|
||||
/* 队列操作:如果last_pkt为空,说明队列是空的,新增节点为队头;
|
||||
* 否则,队列有数据,则让原队尾的next为新增节点。 最后将队尾指向新增节点
|
||||
*/
|
||||
if (!q->last_pkt)
|
||||
q->first_pkt = pkt1;
|
||||
else
|
||||
q->last_pkt->next = pkt1;
|
||||
q->last_pkt = pkt1;
|
||||
|
||||
//队列属性操作:增加节点数、cache大小、cache总时长, 用来控制队列的大小
|
||||
q->nb_packets++;
|
||||
q->size += pkt1->pkt.size + sizeof(*pkt1);
|
||||
q->duration += pkt1->pkt.duration;
|
||||
|
||||
/* XXX: should duplicate packet data in DV case */
|
||||
//发出信号,表明当前队列中有数据了,通知等待中的读线程可以取数据了
|
||||
SDL_CondSignal(q->cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int packet_queue_put(PacketQueue *q, AVPacket *pkt)
|
||||
{
|
||||
int ret;
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
ret = packet_queue_put_private(q, pkt);//主要实现
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
|
||||
if (pkt != &flush_pkt && ret < 0)
|
||||
av_packet_unref(pkt); //放入失败,释放AVPacket
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int packet_queue_put_nullpacket(PacketQueue *q, int stream_index)
|
||||
{
|
||||
AVPacket pkt1, *pkt = &pkt1;
|
||||
av_init_packet(pkt);
|
||||
pkt->data = NULL;
|
||||
pkt->size = 0;
|
||||
pkt->stream_index = stream_index;
|
||||
return packet_queue_put(q, pkt);
|
||||
}
|
||||
|
||||
/* packet queue handling */
|
||||
int packet_queue_init(PacketQueue *q)
|
||||
{
|
||||
memset(q, 0, sizeof(PacketQueue));
|
||||
q->mutex = SDL_CreateMutex();
|
||||
if (!q->mutex) {
|
||||
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
q->cond = SDL_CreateCond();
|
||||
if (!q->cond) {
|
||||
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
q->abort_request = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void packet_queue_flush(PacketQueue *q)
|
||||
{
|
||||
MyAVPacketList *pkt, *pkt1;
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
for (pkt = q->first_pkt; pkt; pkt = pkt1) {
|
||||
pkt1 = pkt->next;
|
||||
av_packet_unref(&pkt->pkt);
|
||||
av_freep(&pkt);
|
||||
}
|
||||
q->last_pkt = NULL;
|
||||
q->first_pkt = NULL;
|
||||
q->nb_packets = 0;
|
||||
q->size = 0;
|
||||
q->duration = 0;
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
|
||||
void packet_queue_destroy(PacketQueue *q)
|
||||
{
|
||||
packet_queue_flush(q); //先清除所有的节点
|
||||
SDL_DestroyMutex(q->mutex);
|
||||
SDL_DestroyCond(q->cond);
|
||||
}
|
||||
|
||||
void packet_queue_abort(PacketQueue *q)
|
||||
{
|
||||
SDL_LockMutex(q->mutex);
|
||||
|
||||
q->abort_request = 1; // 请求退出
|
||||
|
||||
SDL_CondSignal(q->cond); //释放一个条件信号
|
||||
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
|
||||
void packet_queue_start(PacketQueue *q)
|
||||
{
|
||||
SDL_LockMutex(q->mutex);
|
||||
q->abort_request = 0;
|
||||
packet_queue_put_private(q, &flush_pkt); //这里放入了一个flush_pkt
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
|
||||
double packet_queue_cache_duration(PacketQueue *q, AVRational time_base, double packet_duration)
|
||||
{
|
||||
double pts_duration = 0; // 按队列头部、尾部的pts时长
|
||||
double packets_duration = 0;// 按包累积的时长
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
MyAVPacketList *first_pkt = q->first_pkt;
|
||||
MyAVPacketList *last_pkt = q->last_pkt;
|
||||
int64_t temp_pts = last_pkt->pkt.dts - first_pkt->pkt.dts; // 计算出来差距
|
||||
pts_duration = temp_pts * av_q2d(time_base); // 转换成秒
|
||||
|
||||
packets_duration = packet_duration * q->nb_packets; // packet可能存在多帧音频的情况, 此时这里计算就存在误差
|
||||
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
|
||||
// 以时间戳为准,然后根据码率预估持续播放的最大时长,比如以时间戳计算出来超过60秒,则是极有可能是有问题的,说明缓存的数据比较多
|
||||
if(pts_duration < 60) {
|
||||
return pts_duration;
|
||||
} else {
|
||||
return packets_duration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* return < 0 if aborted, 0 if no packet and > 0 if packet. */
|
||||
/**
|
||||
* @brief packet_queue_get
|
||||
* @param q 队列
|
||||
* @param pkt 输出参数,即MyAVPacketList.pkt
|
||||
* @param block 调用者是否需要在没节点可取的情况下阻塞等待
|
||||
* @param serial 输出参数,即MyAVPacketList.serial
|
||||
* @return <0: aborted; =0: no packet; >0: has packet
|
||||
*/
|
||||
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
|
||||
{
|
||||
MyAVPacketList *pkt1;
|
||||
int ret;
|
||||
|
||||
SDL_LockMutex(q->mutex); // 加锁
|
||||
|
||||
for (;;) {
|
||||
if (q->abort_request) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
pkt1 = q->first_pkt; //MyAVPacketList *pkt1; 从队头拿数据
|
||||
if (pkt1) { //队列中有数据
|
||||
q->first_pkt = pkt1->next; //队头移到第二个节点
|
||||
if (!q->first_pkt)
|
||||
q->last_pkt = NULL;
|
||||
q->nb_packets--; //节点数减1
|
||||
q->size -= pkt1->pkt.size + sizeof(*pkt1); //cache大小扣除一个节点
|
||||
q->duration -= pkt1->pkt.duration; //总时长扣除一个节点
|
||||
//返回AVPacket,这里发生一次AVPacket结构体拷贝,AVPacket的data只拷贝了指针
|
||||
*pkt = pkt1->pkt;
|
||||
if (serial) //如果需要输出serial,把serial输出
|
||||
*serial = pkt1->serial;
|
||||
av_free(pkt1); //释放节点内存,只是释放节点,而不是释放AVPacket
|
||||
ret = 1;
|
||||
break;
|
||||
} else if (!block) { //队列中没有数据,且非阻塞调用
|
||||
ret = 0;
|
||||
break;
|
||||
} else { //队列中没有数据,且阻塞调用
|
||||
//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
|
||||
SDL_CondWait(q->cond, q->mutex);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(q->mutex); // 释放锁
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void frame_queue_unref_item(Frame *vp)
|
||||
{
|
||||
av_frame_unref(vp->frame); /* 释放数据 */
|
||||
}
|
||||
|
||||
/* 初始化FrameQueue,视频和音频keep_last设置为1,字幕设置为0 */
|
||||
int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last)
|
||||
{
|
||||
int i;
|
||||
memset(f, 0, sizeof(FrameQueue));
|
||||
if (!(f->mutex = SDL_CreateMutex())) {
|
||||
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
if (!(f->cond = SDL_CreateCond())) {
|
||||
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
|
||||
return AVERROR(ENOMEM);
|
||||
}
|
||||
f->pktq = pktq;
|
||||
f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
|
||||
f->keep_last = !!keep_last;
|
||||
for (i = 0; i < f->max_size; i++)
|
||||
if (!(f->queue[i].frame = av_frame_alloc())) // 分配AVFrame结构体
|
||||
return AVERROR(ENOMEM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void frame_queue_destory(FrameQueue *f)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < f->max_size; i++) {
|
||||
Frame *vp = &f->queue[i];
|
||||
// 释放对vp->frame中的数据缓冲区的引用,注意不是释放frame对象本身
|
||||
frame_queue_unref_item(vp);
|
||||
// 释放vp->frame对象
|
||||
av_frame_free(&vp->frame);
|
||||
}
|
||||
SDL_DestroyMutex(f->mutex);
|
||||
SDL_DestroyCond(f->cond);
|
||||
}
|
||||
|
||||
void frame_queue_signal(FrameQueue *f)
|
||||
{
|
||||
SDL_LockMutex(f->mutex);
|
||||
SDL_CondSignal(f->cond);
|
||||
SDL_UnlockMutex(f->mutex);
|
||||
}
|
||||
|
||||
/* 获取队列当前Frame, 在调用该函数前先调用frame_queue_nb_remaining确保有frame可读 */
|
||||
Frame *frame_queue_peek(FrameQueue *f)
|
||||
{
|
||||
return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
|
||||
}
|
||||
|
||||
/* 获取当前Frame的下一Frame, 此时要确保queue里面至少有2个Frame */
|
||||
// 不管你什么时候调用,返回来肯定不是 NULL
|
||||
Frame *frame_queue_peek_next(FrameQueue *f)
|
||||
{
|
||||
return &f->queue[(f->rindex + f->rindex_shown + 1) % f->max_size];
|
||||
}
|
||||
|
||||
/* 获取last Frame:
|
||||
*/
|
||||
Frame *frame_queue_peek_last(FrameQueue *f)
|
||||
{
|
||||
return &f->queue[f->rindex];
|
||||
}
|
||||
// 获取可写指针
|
||||
Frame *frame_queue_peek_writable(FrameQueue *f)
|
||||
{
|
||||
/* wait until we have space to put a new frame */
|
||||
SDL_LockMutex(f->mutex);
|
||||
while (f->size >= f->max_size &&
|
||||
!f->pktq->abort_request) { /* 检查是否需要退出 */
|
||||
SDL_CondWait(f->cond, f->mutex);
|
||||
}
|
||||
SDL_UnlockMutex(f->mutex);
|
||||
|
||||
if (f->pktq->abort_request) /* 检查是不是要退出 */
|
||||
return NULL;
|
||||
|
||||
return &f->queue[f->windex];
|
||||
}
|
||||
// 获取可读
|
||||
Frame *frame_queue_peek_readable(FrameQueue *f)
|
||||
{
|
||||
/* wait until we have a readable a new frame */
|
||||
SDL_LockMutex(f->mutex);
|
||||
while (f->size <= 0 &&
|
||||
!f->pktq->abort_request) {
|
||||
SDL_CondWait(f->cond, f->mutex);
|
||||
}
|
||||
SDL_UnlockMutex(f->mutex);
|
||||
|
||||
if (f->pktq->abort_request)
|
||||
return NULL;
|
||||
|
||||
return &f->queue[(f->rindex + f->rindex_shown) % f->max_size];
|
||||
}
|
||||
// 更新写指针
|
||||
void frame_queue_push(FrameQueue *f)
|
||||
{
|
||||
if (++f->windex == f->max_size)
|
||||
f->windex = 0;
|
||||
SDL_LockMutex(f->mutex);
|
||||
f->size++;
|
||||
SDL_CondSignal(f->cond); // 当_readable在等待时则可以唤醒
|
||||
SDL_UnlockMutex(f->mutex);
|
||||
}
|
||||
|
||||
/* 释放当前frame,并更新读索引rindex */
|
||||
void frame_queue_next(FrameQueue *f)
|
||||
{
|
||||
if (f->keep_last && !f->rindex_shown) {
|
||||
f->rindex_shown = 1;
|
||||
return;
|
||||
}
|
||||
frame_queue_unref_item(&f->queue[f->rindex]);
|
||||
if (++f->rindex == f->max_size)
|
||||
f->rindex = 0;
|
||||
SDL_LockMutex(f->mutex);
|
||||
f->size--;
|
||||
SDL_CondSignal(f->cond);
|
||||
SDL_UnlockMutex(f->mutex);
|
||||
}
|
||||
|
||||
/* return the number of undisplayed frames in the queue */
|
||||
int frame_queue_nb_remaining(FrameQueue *f)
|
||||
{
|
||||
return f->size - f->rindex_shown;
|
||||
}
|
||||
|
||||
/* return last shown position */
|
||||
int64_t frame_queue_last_pos(FrameQueue *f)
|
||||
{
|
||||
Frame *fp = &f->queue[f->rindex];
|
||||
if (f->rindex_shown && fp->serial == f->pktq->serial)
|
||||
if(fp)
|
||||
return fp->pos;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取到的实际上是:最后一帧的pts 加上 从处理最后一帧开始到现在的时间,具体参考set_clock_at 和get_clock的代码
|
||||
* c->pts_drift=最后一帧的pts-从处理最后一帧时间
|
||||
* clock=c->pts_drift+现在的时候
|
||||
* get_clock(&is->vidclk) ==is->vidclk.pts, av_gettime_relative() / 1000000.0 -is->vidclk.last_updated +is->vidclk.pts
|
||||
*/
|
||||
double get_clock(Clock *c)
|
||||
{
|
||||
if (*c->queue_serial != c->serial)
|
||||
return NAN; // 不是同一个播放序列,时钟是无效
|
||||
if (c->paused) {
|
||||
return c->pts; // 暂停的时候返回的是pts
|
||||
} else {
|
||||
double time = av_gettime_relative() / 1000000.0;
|
||||
return c->pts_drift + time - (time - c->last_updated) * (1.0 - c->speed);
|
||||
}
|
||||
}
|
||||
|
||||
void set_clock_at(Clock *c, double pts, int serial,double time)
|
||||
{
|
||||
c->pts = pts; /* 当前帧的pts */
|
||||
c->last_updated = time; /* 最后更新的时间,实际上是当前的一个系统时间 */
|
||||
c->pts_drift = c->pts - time; /* 当前帧pts和系统时间的差值,正常播放情况下两者的差值应该是比较固定的,因为两者都是以时间为基准进行线性增长 */
|
||||
c->serial = serial;
|
||||
}
|
||||
|
||||
void set_clock(Clock *c, double pts, int serial)
|
||||
{
|
||||
double time = av_gettime_relative() / 1000000.0;
|
||||
set_clock_at(c, pts, serial, time);
|
||||
}
|
||||
|
||||
|
||||
void init_clock(Clock *c, int *queue_serial)
|
||||
{
|
||||
c->speed = 1.0;
|
||||
c->paused = 0;
|
||||
c->queue_serial = queue_serial;
|
||||
set_clock(c, NAN, -1);
|
||||
}
|
||||
|
||||
|
||||
void ffp_reset_statistic(FFStatistic *dcc)
|
||||
{
|
||||
memset(dcc, 0, sizeof(FFStatistic));
|
||||
}
|
||||
259
ff_ffplay_def.h
Normal file
@@ -0,0 +1,259 @@
|
||||
#ifndef FF_FFPLAY_DEF_H
|
||||
#define FF_FFPLAY_DEF_H
|
||||
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
extern "C" {
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/time.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavcodec/avfft.h"
|
||||
#include "libswresample/swresample.h"
|
||||
}
|
||||
#include <SDL.h>
|
||||
#include <SDL_thread.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "ijksdl_timer.h"
|
||||
|
||||
#define MAX_QUEUE_SIZE (15 * 1024 * 1024)
|
||||
#define MIN_FRAMES 25
|
||||
#define EXTERNAL_CLOCK_MIN_FRAMES 2
|
||||
#define EXTERNAL_CLOCK_MAX_FRAMES 10
|
||||
|
||||
/* Step size for volume control in dB */
|
||||
#define SDL_VOLUME_STEP (0.75)
|
||||
|
||||
/* no AV sync correction is done if below the minimum AV sync threshold */
|
||||
#define AV_SYNC_THRESHOLD_MIN 0.04
|
||||
/* AV sync correction is done if above the maximum AV sync threshold */
|
||||
#define AV_SYNC_THRESHOLD_MAX 0.1
|
||||
/* If a frame duration is longer than this, it will not be duplicated to compensate AV sync */
|
||||
#define AV_SYNC_FRAMEDUP_THRESHOLD 0.1
|
||||
/* no AV correction is done if too big error */
|
||||
#define AV_NOSYNC_THRESHOLD 10.0
|
||||
|
||||
|
||||
typedef struct FFTrackCacheStatistic
|
||||
{
|
||||
int64_t duration;
|
||||
int64_t bytes;
|
||||
int64_t packets;
|
||||
} FFTrackCacheStatistic;
|
||||
|
||||
|
||||
typedef struct FFStatistic
|
||||
{
|
||||
int64_t vdec_type;
|
||||
|
||||
float vfps;
|
||||
float vdps;
|
||||
float avdelay;
|
||||
float avdiff;
|
||||
int64_t bit_rate;
|
||||
|
||||
FFTrackCacheStatistic video_cache;
|
||||
FFTrackCacheStatistic audio_cache;
|
||||
|
||||
int64_t buf_backwards;
|
||||
int64_t buf_forwards;
|
||||
int64_t buf_capacity;
|
||||
SDL_SpeedSampler2 tcp_read_sampler;
|
||||
int64_t latest_seek_load_duration;
|
||||
int64_t byte_count;
|
||||
int64_t cache_physical_pos;
|
||||
int64_t cache_file_forwards;
|
||||
int64_t cache_file_pos;
|
||||
int64_t cache_count_bytes;
|
||||
int64_t logical_file_size;
|
||||
int drop_frame_count;
|
||||
int decode_frame_count;
|
||||
float drop_frame_rate;
|
||||
} FFStatistic;
|
||||
|
||||
enum RET_CODE
|
||||
{
|
||||
RET_ERR_UNKNOWN = -2, // 未知错误
|
||||
RET_FAIL = -1, // 失败
|
||||
RET_OK = 0, // 正常
|
||||
RET_ERR_OPEN_FILE, // 打开文件失败
|
||||
RET_ERR_NOT_SUPPORT, // 不支持
|
||||
RET_ERR_OUTOFMEMORY, // 没有内存
|
||||
RET_ERR_STACKOVERFLOW, // 溢出
|
||||
RET_ERR_NULLREFERENCE, // 空参考
|
||||
RET_ERR_ARGUMENTOUTOFRANGE, //
|
||||
RET_ERR_PARAMISMATCH, //
|
||||
RET_ERR_MISMATCH_CODE, // 没有匹配的编解码器
|
||||
RET_ERR_EAGAIN,
|
||||
RET_ERR_EOF
|
||||
};
|
||||
|
||||
|
||||
typedef struct MyAVPacketList {
|
||||
AVPacket pkt; //解封装后的数据
|
||||
struct MyAVPacketList *next; //下一个节点
|
||||
int serial; //播放序列
|
||||
} MyAVPacketList;
|
||||
|
||||
typedef struct PacketQueue {
|
||||
MyAVPacketList *first_pkt, *last_pkt; // 队首,队尾指针
|
||||
int nb_packets; // 包数量,也就是队列元素数量
|
||||
int size; // 队列所有元素的数据大小总和
|
||||
int64_t duration; // 队列所有元素的数据播放持续时间
|
||||
int abort_request; // 用户退出请求标志
|
||||
int serial; // 播放序列号,和MyAVPacketList的serial作用相同,但改变的时序稍微有点不同
|
||||
SDL_mutex *mutex; // 用于维持PacketQueue的多线程安全(SDL_mutex可以按pthread_mutex_t理解)
|
||||
SDL_cond *cond; // 用于读、写线程相互通知(SDL_cond可以按pthread_cond_t理解)
|
||||
} PacketQueue;
|
||||
|
||||
#define VIDEO_PICTURE_QUEUE_SIZE 3 // 图像帧缓存数量
|
||||
#define VIDEO_PICTURE_QUEUE_SIZE_MIN (3)
|
||||
#define VIDEO_PICTURE_QUEUE_SIZE_MAX (16)
|
||||
#define VIDEO_PICTURE_QUEUE_SIZE_DEFAULT (VIDEO_PICTURE_QUEUE_SIZE_MIN)
|
||||
#define SUBPICTURE_QUEUE_SIZE 16 // 字幕帧缓存数量
|
||||
#define SAMPLE_QUEUE_SIZE 9 // 采样帧缓存数量
|
||||
#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE))
|
||||
|
||||
|
||||
typedef struct AudioParams {
|
||||
int freq; // 采样率
|
||||
int channels; // 通道数
|
||||
int64_t channel_layout; // 通道布局,比如2.1声道,5.1声道等
|
||||
enum AVSampleFormat fmt; // 音频采样格式,比如AV_SAMPLE_FMT_S16表示为有符号16bit深度,交错排列模式。
|
||||
int frame_size; // 一个采样单元占用的字节数(比如2通道时,则左右通道各采样一次合成一个采样单元)
|
||||
int bytes_per_sec; // 一秒时间的字节数,比如采样率48Khz,2 channel,16bit,则一秒48000*2*16/8=192000
|
||||
} AudioParams;
|
||||
|
||||
|
||||
/* Common struct for handling all types of decoded data and allocated render buffers. */
|
||||
// 用于缓存解码后的数据
|
||||
typedef struct Frame {
|
||||
AVFrame *frame; // 指向数据帧
|
||||
int serial; // 帧序列,在seek的操作时serial会变化
|
||||
double pts; // 时间戳,单位为秒
|
||||
double duration; // 该帧持续时间,单位为秒
|
||||
int64_t pos;
|
||||
int width; // 图像宽度
|
||||
int height; // 图像高读
|
||||
int format; // 对于图像为(enum AVPixelFormat)
|
||||
AVRational sar;
|
||||
int uploaded;
|
||||
int flip_v;
|
||||
} Frame;
|
||||
|
||||
/* 这是一个循环队列,windex是指其中的首元素,rindex是指其中的尾部元素. */
|
||||
typedef struct FrameQueue {
|
||||
Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的内存,需要注意该值的设置
|
||||
int rindex; // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧
|
||||
int windex; // 写索引
|
||||
int size; // 当前总帧数
|
||||
int max_size; // 可存储最大帧数
|
||||
int keep_last;
|
||||
int rindex_shown;
|
||||
SDL_mutex *mutex; // 互斥量
|
||||
SDL_cond *cond; // 条件变量
|
||||
PacketQueue *pktq; // 数据包缓冲队列
|
||||
} FrameQueue;
|
||||
|
||||
|
||||
// 这里讲的系统时钟 是通过av_gettime_relative()获取到的时钟,单位为微妙
|
||||
typedef struct Clock {
|
||||
double pts; // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧
|
||||
// 当前pts与当前系统时钟的差值, audio、video对于该值是独立的
|
||||
double pts_drift; // clock base minus time at which we updated the clock
|
||||
// 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间
|
||||
double last_updated; // 最后一次更新的系统时钟
|
||||
double speed; // 时钟速度控制,用于控制播放速度
|
||||
// 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列
|
||||
int serial; // clock is based on a packet with this serial
|
||||
int paused; // = 1 说明是暂停状态
|
||||
// 指向packet_serial
|
||||
int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */
|
||||
} Clock;
|
||||
|
||||
/**
|
||||
*音视频同步方式,缺省以音频为基准
|
||||
*/
|
||||
enum {
|
||||
AV_SYNC_UNKNOW_MASTER = -1,
|
||||
AV_SYNC_AUDIO_MASTER, // 以音频为基准
|
||||
AV_SYNC_VIDEO_MASTER, // 以视频为基准
|
||||
// AV_SYNC_EXTERNAL_CLOCK, // 以外部时钟为基准,synchronize to an external clock */
|
||||
};
|
||||
|
||||
#define fftime_to_milliseconds(ts) (av_rescale(ts, 1000, AV_TIME_BASE))
|
||||
#define milliseconds_to_fftime(ms) (av_rescale(ms, AV_TIME_BASE, 1000))
|
||||
|
||||
extern AVPacket flush_pkt;
|
||||
// 队列相关
|
||||
int packet_queue_put(PacketQueue *q, AVPacket *pkt);
|
||||
int packet_queue_put_nullpacket(PacketQueue *q, int stream_index);
|
||||
int packet_queue_init(PacketQueue *q);
|
||||
void packet_queue_flush(PacketQueue *q);
|
||||
void packet_queue_destroy(PacketQueue *q);
|
||||
void packet_queue_abort(PacketQueue *q);
|
||||
void packet_queue_start(PacketQueue *q);
|
||||
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial);
|
||||
|
||||
/**
|
||||
* @brief 获取帧缓存的数据可以播放的时间长度
|
||||
* @param q 队列本身
|
||||
* @param time_base 用于计算packet的时间戳转换
|
||||
* @param packet_duration 单个包可以播放的时长
|
||||
* @return 返回时长以秒为单位
|
||||
*/
|
||||
double packet_queue_cache_duration(PacketQueue *q, AVRational time_base, double packet_duration);
|
||||
|
||||
/* 初始化FrameQueue,视频和音频keep_last设置为1,字幕设置为0 */
|
||||
int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last);
|
||||
void frame_queue_destory(FrameQueue *f);
|
||||
void frame_queue_signal(FrameQueue *f);
|
||||
/* 获取队列当前Frame, 在调用该函数前先调用frame_queue_nb_remaining确保有frame可读 */
|
||||
Frame *frame_queue_peek(FrameQueue *f);
|
||||
|
||||
/* 获取当前Frame的下一Frame, 此时要确保queue里面至少有2个Frame */
|
||||
// 不管你什么时候调用,返回来肯定不是 NULL
|
||||
Frame *frame_queue_peek_next(FrameQueue *f);
|
||||
/* 获取last Frame:
|
||||
*/
|
||||
Frame *frame_queue_peek_last(FrameQueue *f);
|
||||
// 获取可写指针
|
||||
Frame *frame_queue_peek_writable(FrameQueue *f);
|
||||
// 获取可读
|
||||
Frame *frame_queue_peek_readable(FrameQueue *f);
|
||||
// 更新写指针
|
||||
void frame_queue_push(FrameQueue *f);
|
||||
/* 释放当前frame,并更新读索引rindex */
|
||||
void frame_queue_next(FrameQueue *f);
|
||||
int frame_queue_nb_remaining(FrameQueue *f);
|
||||
int64_t frame_queue_last_pos(FrameQueue *f);
|
||||
|
||||
|
||||
// 时钟相关
|
||||
double get_clock(Clock *c);
|
||||
void set_clock_at(Clock *c, double pts, int serial, double time);
|
||||
void set_clock(Clock *c, double pts, int serial);
|
||||
void init_clock(Clock *c, int *queue_serial);
|
||||
|
||||
|
||||
void ffp_reset_statistic(FFStatistic *dcc);
|
||||
|
||||
|
||||
|
||||
#endif // FF_FFPLAY_DEF_H
|
||||
93
ffmsg.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef FFMSG_H
|
||||
#define FFMSG_H
|
||||
|
||||
#define FFP_MSG_FLUSH 10
|
||||
#define FFP_MSG_ERROR 100 /*出现错误 arg1 = error */
|
||||
#define FFP_MSG_PREPARED 200 // 准备好了
|
||||
#define FFP_MSG_COMPLETED 300 // 播放完成
|
||||
#define FFP_MSG_VIDEO_SIZE_CHANGED 400 /* 视频大小发送变化 arg1 = width, arg2 = height */
|
||||
#define FFP_MSG_SAR_CHANGED 401 /* arg1 = sar.num, arg2 = sar.den */
|
||||
#define FFP_MSG_VIDEO_RENDERING_START 402 //开始画面渲染
|
||||
#define FFP_MSG_AUDIO_RENDERING_START 403 //开始声音输出
|
||||
#define FFP_MSG_VIDEO_ROTATION_CHANGED 404 /* arg1 = degree */
|
||||
#define FFP_MSG_AUDIO_DECODED_START 405 // 开始音频解码
|
||||
#define FFP_MSG_VIDEO_DECODED_START 406 // 开始视频解码
|
||||
#define FFP_MSG_OPEN_INPUT 407 // read_thread 调用了 avformat_open_input
|
||||
#define FFP_MSG_FIND_STREAM_INFO 408 // read_thread 调用了 avformat_find_stream_info
|
||||
#define FFP_MSG_COMPONENT_OPEN 409 // read_thread 调用了 stream_component_open
|
||||
#define FFP_MSG_COMPONENT_OPEN 409
|
||||
#define FFP_MSG_VIDEO_SEEK_RENDERING_START 410
|
||||
#define FFP_MSG_AUDIO_SEEK_RENDERING_START 411
|
||||
|
||||
#define FFP_MSG_BUFFERING_START 500
|
||||
#define FFP_MSG_BUFFERING_END 501
|
||||
#define FFP_MSG_BUFFERING_UPDATE 502 /* arg1 = buffering head position in time, arg2 = minimum percent in time or bytes */
|
||||
#define FFP_MSG_BUFFERING_BYTES_UPDATE 503 /* arg1 = cached data in bytes, arg2 = high water mark */
|
||||
#define FFP_MSG_BUFFERING_TIME_UPDATE 504 /* arg1 = cached duration in milliseconds, arg2 = high water mark */
|
||||
#define FFP_MSG_SEEK_COMPLETE 600 /* arg1 = seek position, arg2 = error */
|
||||
#define FFP_MSG_PLAYBACK_STATE_CHANGED 700
|
||||
#define FFP_MSG_TIMED_TEXT 800
|
||||
#define FFP_MSG_ACCURATE_SEEK_COMPLETE 900 /* arg1 = current position*/
|
||||
#define FFP_MSG_GET_IMG_STATE 1000 /* arg1 = timestamp, arg2 = result code, obj = file name*/
|
||||
#define FFP_MSG_SCREENSHOT_COMPLETE 1100 // 截屏完成
|
||||
#define FFP_MSG_PLAY_FNISH 1200 //数据都播放完了,通知ui停止播放
|
||||
#define FFP_MSG_VIDEO_DECODER_OPEN 10001
|
||||
|
||||
|
||||
#define FFP_REQ_START 20001 // 核心播放器已经准备好了,请求ui模块调用start
|
||||
#define FFP_REQ_PAUSE 20002 // ui模块请求暂停 恢复都是同样的命令
|
||||
#define FFP_REQ_SEEK 20003 // ui模块请求seek位置
|
||||
#define FFP_REQ_SCREENSHOT 20004 // 截屏请求
|
||||
#define FFP_REQ_FORWARD 20005
|
||||
#define FFP_REQ_BACK 20006
|
||||
|
||||
|
||||
// 这里的命令是获取属性的,和msg不是同一套逻辑
|
||||
#define FFP_PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND 10001
|
||||
#define FFP_PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND 10002
|
||||
#define FFP_PROP_FLOAT_PLAYBACK_RATE 10003
|
||||
#define FFP_PROP_FLOAT_PLAYBACK_VOLUME 10006
|
||||
#define FFP_PROP_FLOAT_AVDELAY 10004
|
||||
#define FFP_PROP_FLOAT_AVDIFF 10005
|
||||
#define FFP_PROP_FLOAT_DROP_FRAME_RATE 10007
|
||||
|
||||
#define FFP_PROP_INT64_SELECTED_VIDEO_STREAM 20001
|
||||
#define FFP_PROP_INT64_SELECTED_AUDIO_STREAM 20002
|
||||
#define FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM 20011
|
||||
#define FFP_PROP_INT64_VIDEO_DECODER 20003
|
||||
#define FFP_PROP_INT64_AUDIO_DECODER 20004
|
||||
#define FFP_PROPV_DECODER_UNKNOWN 0
|
||||
#define FFP_PROPV_DECODER_AVCODEC 1
|
||||
#define FFP_PROPV_DECODER_MEDIACODEC 2
|
||||
#define FFP_PROPV_DECODER_VIDEOTOOLBOX 3
|
||||
#define FFP_PROP_INT64_VIDEO_CACHED_DURATION 20005
|
||||
#define FFP_PROP_INT64_AUDIO_CACHED_DURATION 20006
|
||||
#define FFP_PROP_INT64_VIDEO_CACHED_BYTES 20007
|
||||
#define FFP_PROP_INT64_AUDIO_CACHED_BYTES 20008
|
||||
#define FFP_PROP_INT64_VIDEO_CACHED_PACKETS 20009
|
||||
#define FFP_PROP_INT64_AUDIO_CACHED_PACKETS 20010
|
||||
|
||||
#define FFP_PROP_INT64_BIT_RATE 20100
|
||||
|
||||
#define FFP_PROP_INT64_TCP_SPEED 20200
|
||||
|
||||
#define FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS 20201
|
||||
#define FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS 20202
|
||||
#define FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY 20203
|
||||
#define FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT 20204
|
||||
|
||||
#define FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION 20300
|
||||
|
||||
#define FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS 20205
|
||||
|
||||
#define FFP_PROP_INT64_CACHE_STATISTIC_FILE_FORWARDS 20206
|
||||
|
||||
#define FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS 20207
|
||||
|
||||
#define FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES 20208
|
||||
|
||||
#define FFP_PROP_INT64_LOGICAL_FILE_SIZE 20209
|
||||
#define FFP_PROP_INT64_SHARE_CACHE_DATA 20210
|
||||
#define FFP_PROP_INT64_IMMEDIATE_RECONNECT 20211
|
||||
|
||||
#endif // FFMSG_H
|
||||
262
ffmsg_queue.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
#include "ffmsg_queue.h"
|
||||
extern "C" {
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/time.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavcodec/avfft.h"
|
||||
#include "libswresample/swresample.h"
|
||||
}
|
||||
|
||||
#include "ffmsg.h"
|
||||
void msg_free_res(AVMessage *msg)
|
||||
{
|
||||
if(!msg || !msg->obj)
|
||||
return;
|
||||
msg->free_l(msg->obj);
|
||||
msg->obj = NULL;
|
||||
}
|
||||
|
||||
// 消息队列内部重新去构建 AVMessage(重新申请AVMessage,或者来自于recycle_msg)
|
||||
// 新的消息插入到尾部
|
||||
int msg_queue_put_private(MessageQueue *q, AVMessage *msg)
|
||||
{
|
||||
AVMessage *msg1;
|
||||
|
||||
if(q->abort_request)
|
||||
return -1;
|
||||
|
||||
//1. 消息体使用回收的资源还是重新malloc
|
||||
msg1 = q->recycle_msg;
|
||||
if(msg1) {
|
||||
q->recycle_msg = msg1->next;
|
||||
q->recycle_count++;
|
||||
} else {
|
||||
q->alloc_count++;
|
||||
msg1 = (AVMessage *)av_malloc(sizeof(AVMessage));
|
||||
}
|
||||
|
||||
*msg1 = *msg;
|
||||
msg1->next = NULL;
|
||||
|
||||
if(!q->first_msg) {
|
||||
q->first_msg = msg1;
|
||||
} else {
|
||||
q->last_msg->next = msg1;
|
||||
}
|
||||
|
||||
q->last_msg = msg1;
|
||||
q->nb_messages++;
|
||||
SDL_CondSignal(q->cond);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int msg_queue_put(MessageQueue *q, AVMessage *msg)
|
||||
{
|
||||
int ret;
|
||||
SDL_LockMutex(q->mutex);
|
||||
ret = msg_queue_put_private(q, msg);
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void msg_init_msg(AVMessage *msg)
|
||||
{
|
||||
memset(msg, 0, sizeof(AVMessage));
|
||||
}
|
||||
|
||||
|
||||
void msg_queue_put_simple1(MessageQueue *q, int what)
|
||||
{
|
||||
AVMessage msg;
|
||||
msg_init_msg(&msg);
|
||||
msg.what = what;
|
||||
msg_queue_put(q, &msg);
|
||||
}
|
||||
|
||||
void msg_queue_put_simple2(MessageQueue *q, int what, int arg1)
|
||||
{
|
||||
|
||||
AVMessage msg;
|
||||
msg_init_msg(&msg);
|
||||
msg.what = what;
|
||||
msg.arg1 = arg1;
|
||||
msg_queue_put(q, &msg);
|
||||
}
|
||||
// 插入简单消息,只带消息类型,带2个参数
|
||||
void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2)
|
||||
{
|
||||
AVMessage msg;
|
||||
msg_init_msg(&msg);
|
||||
msg.what = what;
|
||||
msg.arg1 = arg1;
|
||||
msg.arg2 = arg2;
|
||||
msg_queue_put(q, &msg);
|
||||
}
|
||||
// 释放msg的obj资源
|
||||
void msg_obj_free_l(void *obj)
|
||||
{
|
||||
av_free(obj);
|
||||
}
|
||||
//插入消息,带消息类型,带2个参数,带obj
|
||||
void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len)
|
||||
{
|
||||
AVMessage msg;
|
||||
msg_init_msg(&msg);
|
||||
msg.what = what;
|
||||
msg.arg1 = arg1;
|
||||
msg.arg2 = arg2;
|
||||
msg.obj = av_malloc(obj_len);
|
||||
memcpy(msg.obj, obj, obj_len);
|
||||
msg.free_l = msg_obj_free_l;
|
||||
msg_queue_put(q, &msg);
|
||||
}
|
||||
// 消息队列初始化
|
||||
void msg_queue_init(MessageQueue *q)
|
||||
{
|
||||
memset(q, 0, sizeof(MessageQueue));
|
||||
q->mutex = SDL_CreateMutex();
|
||||
q->cond = SDL_CreateCond();
|
||||
q->abort_request = 1;
|
||||
}
|
||||
// 消息队列flush,清空所有的消息
|
||||
void msg_queue_flush(MessageQueue *q)
|
||||
{
|
||||
AVMessage *msg, *msg1;
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
for (msg = q->first_msg; msg != NULL; msg = msg1) { // 这个时候的obj没有清空?那会导致泄漏,实际是把消息对象暂存到了recycle_msg
|
||||
msg1 = msg->next;
|
||||
msg->next = q->recycle_msg;
|
||||
q->recycle_msg = msg;
|
||||
}
|
||||
q->last_msg = NULL;
|
||||
q->first_msg = NULL;
|
||||
q->nb_messages = 0;
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
|
||||
}
|
||||
// 消息销毁
|
||||
void msg_queue_destroy(MessageQueue *q)
|
||||
{
|
||||
msg_queue_flush(q);
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
while(q->recycle_msg) {
|
||||
AVMessage *msg = q->recycle_msg;
|
||||
if (msg)
|
||||
q->recycle_msg = msg->next;
|
||||
msg_free_res(msg);
|
||||
av_freep(&msg);
|
||||
}
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
|
||||
SDL_DestroyMutex(q->mutex);
|
||||
SDL_DestroyCond(q->cond);
|
||||
}
|
||||
// 消息队列终止
|
||||
void msg_queue_abort(MessageQueue *q)
|
||||
{
|
||||
SDL_LockMutex(q->mutex);
|
||||
q->abort_request = 1;
|
||||
SDL_CondSignal(q->cond);
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
// 启用消息队列
|
||||
void msg_queue_start(MessageQueue *q)
|
||||
{
|
||||
SDL_LockMutex(q->mutex);
|
||||
q->abort_request = 0;
|
||||
// 插入一个消息
|
||||
AVMessage msg;
|
||||
msg_init_msg(&msg);
|
||||
msg.what = FFP_MSG_FLUSH;
|
||||
msg_queue_put_private(q, &msg);
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
|
||||
|
||||
// 从头部first_msg取消息
|
||||
//return < 0 if aborted, 0 if no msg and > 0 if msg.
|
||||
int msg_queue_get(MessageQueue *q, AVMessage *msg, int block)
|
||||
{
|
||||
AVMessage *msg1;
|
||||
int ret;
|
||||
|
||||
SDL_LockMutex(q->mutex);
|
||||
|
||||
for(;;) {
|
||||
if(q->abort_request) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
//获取消息
|
||||
msg1 = q->first_msg;
|
||||
if(msg1) {
|
||||
q->first_msg = msg1->next;
|
||||
if(!q->first_msg)
|
||||
q->last_msg = NULL;
|
||||
q->nb_messages--;
|
||||
*msg = *msg1;
|
||||
msg1->obj = NULL;
|
||||
msg1->next = q->recycle_msg;
|
||||
q->recycle_msg = msg1;
|
||||
ret =1;
|
||||
break; // 记得这里有个break的
|
||||
} else if (!block) {
|
||||
ret = 0;
|
||||
break;
|
||||
} else {
|
||||
SDL_CondWait(q->cond, q->mutex);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
return ret;
|
||||
}
|
||||
// 消息删除 把队列里同一消息类型的消息全删除掉
|
||||
void msg_queue_remove(MessageQueue *q, int what)
|
||||
{
|
||||
AVMessage **p_msg, *msg, *last_msg;
|
||||
SDL_LockMutex(q->mutex);
|
||||
|
||||
last_msg = q->first_msg;
|
||||
|
||||
if (!q->abort_request && q->first_msg) {
|
||||
p_msg = &q->first_msg;
|
||||
while (*p_msg) {
|
||||
msg = *p_msg;
|
||||
if (msg->what == what) { // 同类型的消息全部删除
|
||||
*p_msg = msg->next;
|
||||
msg_free_res(msg);
|
||||
msg->next = q->recycle_msg; // 消息体回收
|
||||
q->recycle_msg = msg;
|
||||
q->nb_messages--;
|
||||
} else {
|
||||
last_msg = msg;
|
||||
p_msg = &msg->next;
|
||||
}
|
||||
}
|
||||
|
||||
if (q->first_msg) {
|
||||
q->last_msg = last_msg;
|
||||
} else {
|
||||
q->last_msg = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(q->mutex);
|
||||
}
|
||||
63
ffmsg_queue.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef FFMSG_QUEUE_H
|
||||
#define FFMSG_QUEUE_H
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
typedef struct AVMessage {
|
||||
int what; // 消息类型
|
||||
int arg1; // 参数1
|
||||
int arg2; // 参数2
|
||||
void *obj; // 如果arg1 arg2还不够存储消息则使用该参数
|
||||
void (*free_l)(void *obj); // obj的对象是分配的,这里要给出函数怎么释放
|
||||
struct AVMessage *next; // 下一个消息
|
||||
} AVMessage;
|
||||
|
||||
|
||||
typedef struct MessageQueue { // 消息队列
|
||||
AVMessage *first_msg, *last_msg; // 消息头,消息尾部
|
||||
int nb_messages; // 有多少个消息
|
||||
int abort_request; // 请求终止消息队列
|
||||
SDL_mutex *mutex; // 互斥量
|
||||
SDL_cond *cond; // 条件变量
|
||||
AVMessage *recycle_msg; // 消息循环使用
|
||||
int recycle_count; // 循环的次数,利用局部性原理
|
||||
int alloc_count; // 分配的次数
|
||||
} MessageQueue;
|
||||
|
||||
|
||||
// 释放msg的obj资源
|
||||
void msg_free_res(AVMessage *msg);
|
||||
// 私有插入消息
|
||||
int msg_queue_put_private(MessageQueue *q, AVMessage *msg);
|
||||
//插入消息
|
||||
int msg_queue_put(MessageQueue *q, AVMessage *msg);
|
||||
// 初始化消息
|
||||
void msg_init_msg(AVMessage *msg);
|
||||
// 插入简单消息,只带消息类型,不带参数
|
||||
void msg_queue_put_simple1(MessageQueue *q, int what);
|
||||
// 插入简单消息,只带消息类型,只带1个参数
|
||||
void msg_queue_put_simple2(MessageQueue *q, int what, int arg1);
|
||||
// 插入简单消息,只带消息类型,带2个参数
|
||||
void msg_queue_put_simple3(MessageQueue *q, int what, int arg1, int arg2);
|
||||
// 释放msg的obj资源
|
||||
void msg_obj_free_l(void *obj);
|
||||
//插入消息,带消息类型,带2个参数,带obj
|
||||
void msg_queue_put_simple4(MessageQueue *q, int what, int arg1, int arg2, void *obj, int obj_len);
|
||||
// 消息队列初始化
|
||||
void msg_queue_init(MessageQueue *q);
|
||||
// 消息队列flush,清空所有的消息
|
||||
void msg_queue_flush(MessageQueue *q);
|
||||
// 消息销毁
|
||||
void msg_queue_destroy(MessageQueue *q);
|
||||
// 消息队列终止
|
||||
void msg_queue_abort(MessageQueue *q);
|
||||
// 启用消息队列
|
||||
void msg_queue_start(MessageQueue *q);
|
||||
// 读取消息
|
||||
/* return < 0 if aborted, 0 if no msg and > 0 if msg. */
|
||||
int msg_queue_get(MessageQueue *q, AVMessage *msg, int block);
|
||||
// 消息删除 把队列里同一消息类型的消息全删除掉
|
||||
void msg_queue_remove(MessageQueue *q, int what);
|
||||
|
||||
|
||||
#endif // FFMSG_QUEUE_H
|
||||
103
globalhelper.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <QFile>
|
||||
#include <QSettings>
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
|
||||
#include "globalhelper.h"
|
||||
#include "easylogging++.h"
|
||||
|
||||
const QString PLAYER_CONFIG_BASEDIR = QDir::tempPath();
|
||||
|
||||
const QString PLAYER_CONFIG = "player_config.ini";
|
||||
|
||||
const QString APP_VERSION = "0.1.0";
|
||||
|
||||
GlobalHelper::GlobalHelper()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QString GlobalHelper::GetQssStr(QString strQssPath)
|
||||
{
|
||||
QString strQss;
|
||||
QFile FileQss(strQssPath);
|
||||
if (FileQss.open(QIODevice::ReadOnly))
|
||||
{
|
||||
strQss = FileQss.readAll();
|
||||
FileQss.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(ERROR) << "读取样式表失败" << strQssPath.toStdString();
|
||||
}
|
||||
return strQss;
|
||||
}
|
||||
|
||||
void GlobalHelper::SetIcon(QPushButton* btn, int iconSize, QChar icon)
|
||||
{
|
||||
QFont font;
|
||||
font.setFamily("FontAwesome");
|
||||
font.setPointSize(iconSize);
|
||||
|
||||
btn->setFont(font);
|
||||
btn->setText(icon);
|
||||
}
|
||||
|
||||
void GlobalHelper::SavePlaylist(QStringList& playList)
|
||||
{
|
||||
//QString strPlayerConfigFileName = QCoreApplication::applicationDirPath() + QDir::separator() + PLAYER_CONFIG;
|
||||
QString strPlayerConfigFileName = PLAYER_CONFIG_BASEDIR + QDir::separator() + PLAYER_CONFIG;
|
||||
QSettings settings(strPlayerConfigFileName, QSettings::IniFormat);
|
||||
settings.beginWriteArray("playlist");
|
||||
for (int i = 0; i < playList.size(); ++i)
|
||||
{
|
||||
settings.setArrayIndex(i);
|
||||
settings.setValue("movie", playList.at(i));
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void GlobalHelper::GetPlaylist(QStringList& playList)
|
||||
{
|
||||
//QString strPlayerConfigFileName = QCoreApplication::applicationDirPath() + QDir::separator() + PLAYER_CONFIG;
|
||||
QString strPlayerConfigFileName = PLAYER_CONFIG_BASEDIR + QDir::separator() + PLAYER_CONFIG;
|
||||
QSettings settings(strPlayerConfigFileName, QSettings::IniFormat);
|
||||
|
||||
int size = settings.beginReadArray("playlist");
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
settings.setArrayIndex(i);
|
||||
playList.append(settings.value("movie").toString());
|
||||
}
|
||||
settings.endArray();
|
||||
}
|
||||
|
||||
void GlobalHelper::SavePlayVolume(double& nVolume)
|
||||
{
|
||||
QString strPlayerConfigFileName = PLAYER_CONFIG_BASEDIR + QDir::separator() + PLAYER_CONFIG;
|
||||
QSettings settings(strPlayerConfigFileName, QSettings::IniFormat);
|
||||
settings.setValue("volume/size", nVolume);
|
||||
}
|
||||
|
||||
void GlobalHelper::GetPlayVolume(double& nVolume)
|
||||
{
|
||||
QString strPlayerConfigFileName = PLAYER_CONFIG_BASEDIR + QDir::separator() + PLAYER_CONFIG;
|
||||
QSettings settings(strPlayerConfigFileName, QSettings::IniFormat);
|
||||
QString str = settings.value("volume/size").toString();
|
||||
nVolume = settings.value("volume/size", nVolume).toDouble();
|
||||
}
|
||||
|
||||
QString GlobalHelper::GetAppVersion()
|
||||
{
|
||||
return APP_VERSION;
|
||||
}
|
||||
|
||||
void GlobalHelper::SetToolbuttonIcon(QToolButton *tb, QString iconPath, QSize size)
|
||||
{
|
||||
tb->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
tb->setIcon(QIcon(iconPath));
|
||||
tb->setIconSize(size);
|
||||
tb->setAutoRaise(true);
|
||||
tb->setFixedSize(64, 64);
|
||||
}
|
||||
|
||||
89
globalhelper.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* @file globalhelper.h
|
||||
* @date 2018/01/07 10:41
|
||||
*
|
||||
* @author itisyang
|
||||
* @Contact itisyang@gmail.com
|
||||
*
|
||||
* @brief 公共接口
|
||||
* @note
|
||||
*/
|
||||
#ifndef GLOBALHELPER_H
|
||||
#define GLOBALHELPER_H
|
||||
#include <QString>
|
||||
#include <QPushButton>
|
||||
#include <QStringList>
|
||||
#include <QToolButton>
|
||||
#pragma execution_character_set("utf-8")
|
||||
|
||||
enum ERROR_CODE
|
||||
{
|
||||
NoError = 0,
|
||||
ErrorFileInvalid
|
||||
};
|
||||
|
||||
class GlobalHelper // 工具类
|
||||
{
|
||||
public:
|
||||
GlobalHelper();
|
||||
/**
|
||||
* 获取样式表
|
||||
*
|
||||
* @param strQssPath 样式表文件路径
|
||||
* @return 样式表
|
||||
* @note
|
||||
*/
|
||||
static QString GetQssStr(QString strQssPath);
|
||||
|
||||
/**
|
||||
* 为按钮设置显示图标
|
||||
*
|
||||
* @param btn 按钮指针
|
||||
* @param iconSize 图标大小
|
||||
* @param icon 图标字符
|
||||
*/
|
||||
static void SetIcon(QPushButton* btn, int iconSize, QChar icon);
|
||||
|
||||
|
||||
static void SavePlaylist(QStringList& playList); // 保存播放列表
|
||||
static void GetPlaylist(QStringList& playList); // 获取播放列表
|
||||
static void SavePlayVolume(double& nVolume); // 保存音量
|
||||
static void GetPlayVolume(double& nVolume); // 获取音量
|
||||
static QString GetAppVersion();
|
||||
static void SetToolbuttonIcon(QToolButton *tb, QString iconPath, QSize size = QSize(48, 48));
|
||||
};
|
||||
|
||||
//必须加以下内容,否则编译不能通过,为了兼容C和C99标准
|
||||
#ifndef INT64_C
|
||||
#define INT64_C
|
||||
#define UINT64_C
|
||||
#endif
|
||||
|
||||
extern "C"{
|
||||
#include "libavutil/avstring.h"
|
||||
#include "libavutil/eval.h"
|
||||
#include "libavutil/mathematics.h"
|
||||
#include "libavutil/pixdesc.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
#include "libavutil/dict.h"
|
||||
#include "libavutil/parseutils.h"
|
||||
#include "libavutil/samplefmt.h"
|
||||
#include "libavutil/avassert.h"
|
||||
#include "libavutil/time.h"
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavdevice/avdevice.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libavcodec/avfft.h"
|
||||
#include "libswresample/swresample.h"
|
||||
#include "SDL.h"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#define MAX_SLIDER_VALUE 65536
|
||||
|
||||
|
||||
|
||||
#endif // GLOBALHELPER_H
|
||||
752
homewindow.cpp
Normal file
@@ -0,0 +1,752 @@
|
||||
#include "homewindow.h"
|
||||
#include "ui_homewindow.h"
|
||||
#include <QDateTime>
|
||||
#include <QFileDialog>
|
||||
#include <QUrl>
|
||||
#include <QMessageBox>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
#include "ffmsg.h"
|
||||
#include "globalhelper.h"
|
||||
#include "toast.h"
|
||||
#include "urldialog.h"
|
||||
#include "easylogging++.h"
|
||||
|
||||
int64_t get_ms()
|
||||
{
|
||||
std::chrono::milliseconds ms = std::chrono::duration_cast< std::chrono::milliseconds >(
|
||||
std::chrono::system_clock::now().time_since_epoch()
|
||||
);
|
||||
return ms.count();
|
||||
}
|
||||
|
||||
// 只有认定为直播流,才会触发变速机制
|
||||
static int is_realtime(const char *url)
|
||||
{
|
||||
if( !strcmp(url, "rtp")
|
||||
|| !strcmp(url, "rtsp")
|
||||
|| !strcmp(url, "sdp")
|
||||
|| !strcmp(url, "udp")
|
||||
|| !strcmp(url, "rtmp")
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
HomeWindow::HomeWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::HomeWindow),
|
||||
bIsFull(false)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->playList->Init();
|
||||
QString str = QString("%1:%2:%3").arg(0, 2, 10, QLatin1Char('0')).arg(0, 2, 10, QLatin1Char('0')).arg(0, 2, 10, QLatin1Char('0'));
|
||||
ui->curPosition->setText(str);
|
||||
ui->totalDuration->setText(str);
|
||||
// 初始化为0ms
|
||||
|
||||
max_cache_duration_ = 400; // 默认200ms
|
||||
network_jitter_duration_ = 100; // 默认100ms
|
||||
initUi();
|
||||
InitSignalsAndSlots();
|
||||
initButtonIcon();
|
||||
}
|
||||
|
||||
HomeWindow::~HomeWindow()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
void HomeWindow::initUi()
|
||||
{
|
||||
//加载样式
|
||||
QString qss = GlobalHelper::GetQssStr(":/res/qss/homewindow.qss");
|
||||
setStyleSheet(qss);
|
||||
}
|
||||
int HomeWindow::InitSignalsAndSlots()
|
||||
{
|
||||
connect(ui->playList, &Playlist::SigPlay, this, &HomeWindow::play);
|
||||
connect(this, &HomeWindow::sig_stopped, this, &HomeWindow::stop);
|
||||
// 设置play进度条的值
|
||||
ui->playSlider->setMinimum(0);
|
||||
play_slider_max_value = 6000;
|
||||
ui->playSlider->setMaximum(play_slider_max_value);
|
||||
//设置更新音量条的回调
|
||||
connect(this, &HomeWindow::sig_updateCurrentPosition, this, &HomeWindow::on_updateCurrentPosition);
|
||||
// 设置音量进度条的值
|
||||
ui->volumeSlider->setMinimum(0);
|
||||
ui->volumeSlider->setMaximum(100);
|
||||
ui->volumeSlider->setValue(50);
|
||||
connect(ui->playSlider, &CustomSlider::SigCustomSliderValueChanged, this, &HomeWindow::on_playSliderValueChanged);
|
||||
connect(ui->volumeSlider, &CustomSlider::SigCustomSliderValueChanged, this, &HomeWindow::on_volumeSliderValueChanged);
|
||||
// toast提示不能直接在非ui线程显示,所以通过信号槽的方式触发提示
|
||||
// 自定义信号槽变量类型要注册,参考:https://blog.csdn.net/Larry_Yanan/article/details/127686354
|
||||
qRegisterMetaType<Toast::Level>("Toast::Level");
|
||||
connect(this, &HomeWindow::sig_showTips, this, &HomeWindow::on_showTips);
|
||||
qRegisterMetaType<int64_t>("int64_t");
|
||||
connect(this, &HomeWindow::sig_updateAudioCacheDuration, this, &HomeWindow::on_UpdateAudioCacheDuration);
|
||||
connect(this, &HomeWindow::sig_updateVideoCacheDuration, this, &HomeWindow::on_UpdateVideoCacheDuration);
|
||||
// 打开文件
|
||||
connect(ui->openFileAction, &QAction::triggered, this, &HomeWindow::on_openFile);
|
||||
connect(ui->openUrlAction, &QAction::triggered, this, &HomeWindow::on_openNetworkUrl);
|
||||
connect(this, &HomeWindow::sig_updatePlayOrPause, this, &HomeWindow::on_updatePlayOrPause);
|
||||
connect(this, &HomeWindow::sig_troggleFull, ui->display, &DisplayWind::onToggleFullScreen);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HomeWindow::message_loop(void *arg)
|
||||
{
|
||||
// QString tips;
|
||||
IjkMediaPlayer *mp = (IjkMediaPlayer *)arg;
|
||||
// 线程循环
|
||||
LOG(INFO) << "message_loop into";
|
||||
while (1) {
|
||||
AVMessage msg;
|
||||
//取消息队列的消息,如果没有消息就阻塞,直到有消息被发到消息队列。
|
||||
// 这里先用非阻塞,在此线程可以做其他监测任务
|
||||
int retval = mp->ijkmp_get_msg(&msg, 0); // 主要处理Java->C的消息
|
||||
if (retval < 0) {
|
||||
break; // -1 要退出线程循环了
|
||||
}
|
||||
if(retval != 0)
|
||||
switch (msg.what) {
|
||||
case FFP_MSG_FLUSH:
|
||||
LOG(INFO) << " FFP_MSG_FLUSH";
|
||||
break;
|
||||
case FFP_MSG_PREPARED:
|
||||
LOG(INFO) << " FFP_MSG_PREPARED" ;
|
||||
mp->ijkmp_start();
|
||||
// ui->playOrPauseBtn->setText("暂停");
|
||||
break;
|
||||
case FFP_MSG_FIND_STREAM_INFO:
|
||||
LOG(INFO) << " FFP_MSG_FIND_STREAM_INFO";
|
||||
getTotalDuration();
|
||||
break;
|
||||
case FFP_MSG_PLAYBACK_STATE_CHANGED:
|
||||
if(mp_->ijkmp_get_state() == MP_STATE_STARTED) {
|
||||
emit sig_updatePlayOrPause(MP_STATE_STARTED);
|
||||
}
|
||||
if(mp_->ijkmp_get_state() == MP_STATE_PAUSED) {
|
||||
emit sig_updatePlayOrPause(MP_STATE_PAUSED);
|
||||
}
|
||||
break;
|
||||
case FFP_MSG_SEEK_COMPLETE:
|
||||
req_seeking_ = false;
|
||||
// startTimer();
|
||||
break;
|
||||
case FFP_MSG_SCREENSHOT_COMPLETE:
|
||||
if(msg.arg1 == 0 ) {
|
||||
QString tips = QString("截屏成功,存储路径:%1").arg((char*)msg.obj);
|
||||
// tips.sprintf("截屏成功,存储路径:%s", (char *)msg.obj);
|
||||
emit sig_showTips(Toast::INFO, tips);
|
||||
} else {
|
||||
QString tips = QString("截屏失败, ret:%1").arg(msg.arg1);
|
||||
// tips.sprintf("截屏失败, ret:%d", msg.arg1);
|
||||
emit sig_showTips(Toast::WARN, tips);
|
||||
}
|
||||
req_screenshot_ = false;
|
||||
break;
|
||||
case FFP_MSG_PLAY_FNISH:
|
||||
QString tips = QString("播放完毕");
|
||||
// tips.sprintf("播放完毕");
|
||||
emit sig_showTips(Toast::INFO, tips);
|
||||
// 发送播放完毕的信号触发调用停止函数
|
||||
emit sig_stopped(); // 触发停止
|
||||
break;
|
||||
// default:
|
||||
// if(retval != 0) {
|
||||
// LOG(WARNING) << " default " << msg.what ;
|
||||
// }
|
||||
// break;
|
||||
}
|
||||
msg_free_res(&msg);
|
||||
// LOG(INFO) << "message_loop sleep, mp:" << mp;
|
||||
// 先模拟线程运行
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
// 获取缓存的值
|
||||
reqUpdateCacheDuration();
|
||||
// 获取当前播放位置
|
||||
reqUpdateCurrentPosition();
|
||||
}
|
||||
// LOG(INFO) << "message_loop leave";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HomeWindow::OutputVideo(const Frame *frame)
|
||||
{
|
||||
// 调用显示控件
|
||||
return ui->display->Draw(frame);
|
||||
}
|
||||
|
||||
void HomeWindow::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
resizeUI();
|
||||
}
|
||||
|
||||
void HomeWindow::resizeUI()
|
||||
{
|
||||
int width = this->width();
|
||||
int height = this->height();
|
||||
LOG(INFO) << "width: " << width;
|
||||
// 获取当前ctrlwidget的位置
|
||||
QRect rect = ui->ctrlBar->geometry();
|
||||
rect.setY(height - ui->menuBar->height() - rect.height());
|
||||
// LOG(INFO) << "rect: " << rect;
|
||||
rect.setWidth(width);
|
||||
ui->ctrlBar->setGeometry(rect);
|
||||
// 设置setting和listbutton的位置
|
||||
rect = ui->settingBtn->geometry();
|
||||
// 获取 ctrlBar的大小 计算list的 x位置
|
||||
int x1 = ui->ctrlBar->width() - rect.width() - rect.width() / 8 * 2;
|
||||
ui->listBtn->setGeometry(x1, rect.y(), rect.width(), rect.height());
|
||||
// LOG(INFO) << "listBtn: " << ui->listBtn->geometry();
|
||||
// 设置setting button的位置,在listbutton左侧
|
||||
rect = ui->listBtn->geometry();
|
||||
x1 = rect.x() - rect.width() - rect.width() / 8 ;
|
||||
ui->settingBtn->setGeometry(x1, rect.y(), rect.width(), rect.height());
|
||||
// LOG(INFO) << "settingBtn: " << ui->settingBtn->geometry();
|
||||
// 设置 显示画面
|
||||
if(is_show_file_list_) {
|
||||
width = this->width() - ui->playList->width();
|
||||
} else {
|
||||
width = this->width();
|
||||
}
|
||||
height = this->height() - ui->ctrlBar->height() - ui->menuBar->height();
|
||||
// int y1 = ui->menuBar->height();
|
||||
int y1 = 0;
|
||||
ui->display->setGeometry(0, y1, width, height);
|
||||
// 设置文件列表 list
|
||||
if(is_show_file_list_) {
|
||||
ui->playList->setGeometry(ui->display->width(), y1, ui->playList->width(), height);
|
||||
}
|
||||
// 设置播放进度条的长度,设置成和显示控件宽度一致
|
||||
rect = ui->playSlider->geometry();
|
||||
width = ui->display->width() - 5 - 5;
|
||||
rect.setWidth(width);
|
||||
ui->playSlider->setGeometry(5, rect.y(), rect.width(), rect.height());
|
||||
// 设置音量条位置
|
||||
x1 = this->width() - 5 - ui->volumeSlider->width();
|
||||
rect = ui->volumeSlider->geometry();
|
||||
ui->volumeSlider->setGeometry(x1, rect.y(), rect.width(), rect.height());
|
||||
}
|
||||
|
||||
void HomeWindow::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
//添加退出事件处理
|
||||
// 弹出一个确认对话框
|
||||
QMessageBox::StandardButton reply;
|
||||
reply = QMessageBox::question(this, "确认退出", "您确定要退出吗?",
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (reply == QMessageBox::Yes) {
|
||||
// 用户选择退出,执行清理操作
|
||||
LOG(INFO) << "执行清理操作...";
|
||||
stop(); //先关闭现有的播放器
|
||||
event->accept(); // 接受关闭事件,窗口关闭
|
||||
} else {
|
||||
// 用户选择不退出,忽略关闭事件
|
||||
event->ignore(); // 忽略关闭事件,窗口保持打开
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_UpdateAudioCacheDuration(int64_t duration)
|
||||
{
|
||||
// ui->audioBufEdit->setText(QString("%1ms").arg(duration));
|
||||
}
|
||||
|
||||
void HomeWindow::on_UpdateVideoCacheDuration(int64_t duration)
|
||||
{
|
||||
// ui->videoBufEdit->setText(QString("%1ms").arg(duration));
|
||||
}
|
||||
|
||||
void HomeWindow::on_openFile()
|
||||
{
|
||||
QUrl url = QFileDialog::getOpenFileName(this, QStringLiteral("选择路径"), QDir::homePath(),
|
||||
nullptr,
|
||||
nullptr, QFileDialog::DontUseCustomDirectoryIcons);
|
||||
if(!url.toString().isEmpty()) {
|
||||
QFileInfo fileInfo(url.toString());
|
||||
// 停止状态下启动播放
|
||||
int ret = play(fileInfo.filePath().toStdString().c_str());
|
||||
// 并把该路径加入播放列表
|
||||
ui->playList->OnAddFile(url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_openNetworkUrl()
|
||||
{
|
||||
UrlDialog urlDialog(this);
|
||||
int nResult = urlDialog.exec();
|
||||
if(nResult == QDialog::Accepted) {
|
||||
//
|
||||
QString url = urlDialog.GetUrl();
|
||||
// LOG(INFO) << "Add url ok, url: " << url.toStdString();
|
||||
if(!url.isEmpty()) {
|
||||
// LOG(INFO) << "SigAddFile url: " << url;
|
||||
// emit SigAddFile(url);
|
||||
ui->playList->AddNetworkUrl(url);
|
||||
int ret = play(url.toStdString());
|
||||
}
|
||||
} else {
|
||||
LOG(INFO) << "Add url no";
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::resizeCtrlBar()
|
||||
{
|
||||
}
|
||||
|
||||
void HomeWindow::resizeDisplayAndFileList()
|
||||
{
|
||||
}
|
||||
|
||||
int HomeWindow::seek(int cur_valule)
|
||||
{
|
||||
if(mp_) {
|
||||
// 打印当前的值
|
||||
LOG(INFO) << "cur value " << cur_valule;
|
||||
double percent = cur_valule * 1.0 / play_slider_max_value;
|
||||
req_seeking_ = true;
|
||||
int64_t milliseconds = percent * total_duration_;
|
||||
mp_->ijkmp_seek_to(milliseconds);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int HomeWindow::fastForward(long inrc)
|
||||
{
|
||||
if(mp_) {
|
||||
mp_->ijkmp_forward_to(inrc);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int HomeWindow::fastBack(long inrc)
|
||||
{
|
||||
if(mp_) {
|
||||
mp_->ijkmp_back_to(inrc);
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::getTotalDuration()
|
||||
{
|
||||
if(mp_) {
|
||||
total_duration_ = mp_->ijkmp_get_duration();
|
||||
// 更新信息
|
||||
// 当前播放位置,总时长
|
||||
long seconds = total_duration_ / 1000;
|
||||
int hour = int(seconds / 3600);
|
||||
int min = int((seconds - hour * 3600) / 60);
|
||||
int sec = seconds % 60;
|
||||
//QString格式化arg前面自动补0
|
||||
QString str = QString("%1:%2:%3").arg(hour, 2, 10, QLatin1Char('0')).arg(min, 2, 10, QLatin1Char('0')).arg(sec, 2, 10, QLatin1Char('0'));
|
||||
ui->totalDuration->setText(str);
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::reqUpdateCurrentPosition()
|
||||
{
|
||||
int64_t cur_time = get_ms();
|
||||
if(cur_time - pre_get_cur_pos_time_ > 500) {
|
||||
pre_get_cur_pos_time_ = cur_time;
|
||||
// 播放器启动,并且不是请求seek的时候才去读取最新的播放位置
|
||||
// LOG(INFO) << "reqUpdateCurrentPosition ";
|
||||
if(mp_ && !req_seeking_) {
|
||||
current_position_ = mp_->ijkmp_get_current_position();
|
||||
// LOG(INFO) << "current_position_ " << current_position_;
|
||||
emit sig_updateCurrentPosition(current_position_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::reqUpdateCacheDuration()
|
||||
{
|
||||
int64_t cur_time = get_ms();
|
||||
if(cur_time - pre_get_cache_time_ > 500) {
|
||||
pre_get_cache_time_ = cur_time;
|
||||
if(mp_) {
|
||||
audio_cache_duration = mp_->ijkmp_get_property_int64(FFP_PROP_INT64_AUDIO_CACHED_DURATION, 0);
|
||||
video_cache_duration = mp_->ijkmp_get_property_int64(FFP_PROP_INT64_VIDEO_CACHED_DURATION, 0);
|
||||
emit sig_updateAudioCacheDuration(audio_cache_duration);
|
||||
emit sig_updateVideoCacheDuration(video_cache_duration);
|
||||
//是否触发变速播放取决于是不是实时流,这里由业务判断,目前主要是判断rtsp、rtmp、rtp流为直播流,有些比较难判断,比如httpflv既可以做直播也可以是点播
|
||||
if(real_time_ ) {
|
||||
if(audio_cache_duration > max_cache_duration_ + network_jitter_duration_) {
|
||||
// 请求开启变速播放
|
||||
mp_->ijkmp_set_playback_rate(accelerate_speed_factor_);
|
||||
is_accelerate_speed_ = true;
|
||||
}
|
||||
if(is_accelerate_speed_ && audio_cache_duration < max_cache_duration_) {
|
||||
mp_->ijkmp_set_playback_rate(normal_speed_factor_);
|
||||
is_accelerate_speed_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::initButtonIcon()
|
||||
{
|
||||
GlobalHelper::SetToolbuttonIcon(ui->playOrPauseBtn, ":/icon/res/icon/play.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->stopBtn, ":/icon/res/icon/stop-button.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->fullBtn, ":/icon/res/icon/resize.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->prevBtn, ":/icon/res/icon/previous.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->nextBtn, ":/icon/res/icon/next.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->screenBtn, ":/icon/res/icon/screenshot.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->forwardFastBtn, ":/icon/res/icon/fast-forward.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->backFastBtn, ":/icon/res/icon/fast-backward.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->listBtn, ":/icon/res/icon/rules.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->settingBtn, ":/icon/res/icon/tune.png");
|
||||
GlobalHelper::SetToolbuttonIcon(ui->speedBtn, ":/icon/res/icon/quick.png");
|
||||
}
|
||||
|
||||
void HomeWindow::on_listBtn_clicked()
|
||||
{
|
||||
if(is_show_file_list_) {
|
||||
is_show_file_list_ = false;
|
||||
ui->playList->hide();
|
||||
} else {
|
||||
is_show_file_list_ = true;
|
||||
ui->playList->show();
|
||||
}
|
||||
resizeUI();
|
||||
}
|
||||
|
||||
void HomeWindow::on_playOrPauseBtn_clicked()
|
||||
{
|
||||
LOG(INFO) << "OnPlayOrPause call";
|
||||
bool ret = false;
|
||||
if(!mp_) {
|
||||
std::string url = ui->playList->GetCurrentUrl();
|
||||
if(!url.empty()) {
|
||||
// 停止状态下启动播放
|
||||
ret = play(url);
|
||||
}
|
||||
} else {
|
||||
if(mp_->ijkmp_get_state() == MP_STATE_STARTED) {
|
||||
// 设置为暂停暂停
|
||||
mp_->ijkmp_pause();
|
||||
// ui->playOrPauseBtn->setText("播放");
|
||||
} else if(mp_->ijkmp_get_state() == MP_STATE_PAUSED) {
|
||||
// 恢复播放
|
||||
mp_->ijkmp_start();
|
||||
// ui->playOrPauseBtn->setText("暂停");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_updatePlayOrPause(int state)
|
||||
{
|
||||
if(state == MP_STATE_STARTED) {
|
||||
ui->playOrPauseBtn->setText("暂停");
|
||||
} else {
|
||||
ui->playOrPauseBtn->setText("播放");
|
||||
}
|
||||
LOG(INFO) << "play state: " << state;
|
||||
}
|
||||
|
||||
void HomeWindow::on_stopBtn_clicked()
|
||||
{
|
||||
LOG(INFO) << "OnStop call";
|
||||
stop();
|
||||
}
|
||||
|
||||
// stop -> play的转换
|
||||
bool HomeWindow::play(std::string url)
|
||||
{
|
||||
int ret = 0;
|
||||
// 如果本身处于播放状态则先停止原有的播放
|
||||
if(mp_) {
|
||||
stop();
|
||||
}
|
||||
// 1. 先检测mp是否已经创建
|
||||
real_time_ = is_realtime(url.c_str());
|
||||
is_accelerate_speed_ = false;
|
||||
mp_ = new IjkMediaPlayer();
|
||||
//1.1 创建
|
||||
ret = mp_->ijkmp_create(std::bind(&HomeWindow::message_loop, this, std::placeholders::_1));
|
||||
if(ret < 0) {
|
||||
LOG(ERROR) << "IjkMediaPlayer create failed";
|
||||
delete mp_;
|
||||
mp_ = NULL;
|
||||
return false;
|
||||
}
|
||||
mp_->AddVideoRefreshCallback(std::bind(&HomeWindow::OutputVideo, this,
|
||||
std::placeholders::_1));
|
||||
// 1.2 设置url
|
||||
mp_->ijkmp_set_data_source(url.c_str());
|
||||
mp_->ijkmp_set_playback_volume(ui->volumeSlider->value());
|
||||
// 1.3 准备工作
|
||||
ret = mp_->ijkmp_prepare_async();
|
||||
if(ret < 0) {
|
||||
LOG(ERROR) << "IjkMediaPlayer create failed";
|
||||
delete mp_;
|
||||
mp_ = NULL;
|
||||
return false;
|
||||
}
|
||||
ui->display->StartPlay();
|
||||
startTimer();
|
||||
return true;
|
||||
}
|
||||
|
||||
//void HomeWindow::on_playSliderValueChanged()
|
||||
//{
|
||||
// LOG(INFO) << "on_playSliderValueChanged" ;
|
||||
//}
|
||||
|
||||
//void HomeWindow::on_volumeSliderValueChanged()
|
||||
//{
|
||||
// LOG(INFO) << "on_volumeSliderValueChanged" ;
|
||||
//}
|
||||
|
||||
void HomeWindow::on_updateCurrentPosition(long position)
|
||||
{
|
||||
// 更新信息
|
||||
// 当前播放位置,总时长
|
||||
long seconds = position / 1000;
|
||||
int hour = int(seconds / 3600);
|
||||
int min = int((seconds - hour * 3600) / 60);
|
||||
int sec = seconds % 60;
|
||||
//QString格式化arg前面自动补0
|
||||
QString str = QString("%1:%2:%3").arg(hour, 2, 10, QLatin1Char('0')).arg(min, 2, 10, QLatin1Char('0')).arg(sec, 2, 10, QLatin1Char('0'));
|
||||
if((position <= total_duration_) // 如果不是直播,那播放时间该<= 总时长
|
||||
|| (total_duration_ == 0)) { // 如果是直播,此时total_duration_为0
|
||||
ui->curPosition->setText(str);
|
||||
}
|
||||
// 更新进度条
|
||||
if(total_duration_ > 0) {
|
||||
int pos = current_position_ * 1.0 / total_duration_ * ui->playSlider->maximum();
|
||||
ui->playSlider->setValue(pos);
|
||||
}
|
||||
}
|
||||
|
||||
//void HomeWindow::on_playSlider_valueChanged(int value)
|
||||
//{
|
||||
// LOG(INFO) << "on_playSlider_valueChanged" ;
|
||||
// // seek();
|
||||
//}
|
||||
|
||||
void HomeWindow::onTimeOut()
|
||||
{
|
||||
if(mp_) {
|
||||
// reqUpdateCurrentPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_playSliderValueChanged(int value)
|
||||
{
|
||||
seek(value);
|
||||
}
|
||||
|
||||
void HomeWindow::on_volumeSliderValueChanged(int value)
|
||||
{
|
||||
if(mp_) {
|
||||
mp_->ijkmp_set_playback_volume(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool HomeWindow::stop()
|
||||
{
|
||||
if(mp_) {
|
||||
stopTimer();
|
||||
mp_->ijkmp_stop();
|
||||
mp_->ijkmp_destroy();
|
||||
delete mp_;
|
||||
mp_ = NULL;
|
||||
real_time_ = 0;
|
||||
is_accelerate_speed_ = false;
|
||||
ui->display->StopPlay(); // 停止渲染,后续刷黑屏
|
||||
ui->playOrPauseBtn->setText("播放");
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_speedBtn_clicked()
|
||||
{
|
||||
if(mp_) {
|
||||
// 先获取当前的倍速,每次叠加0.5, 支持0.5~2.0倍速
|
||||
float rate = mp_->ijkmp_get_playback_rate() + 0.5;
|
||||
if(rate > 2.0) {
|
||||
rate = 0.5;
|
||||
}
|
||||
mp_->ijkmp_set_playback_rate(rate);
|
||||
ui->speedBtn->setText(QString("倍速:%1").arg(rate));
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::startTimer()
|
||||
{
|
||||
if(play_time_) {
|
||||
play_time_->stop();
|
||||
delete play_time_;
|
||||
play_time_ = nullptr;
|
||||
}
|
||||
play_time_ = new QTimer();
|
||||
play_time_->setInterval(1000); // 1秒触发一次
|
||||
connect(play_time_, SIGNAL(timeout()), this, SLOT(onTimeOut()));
|
||||
play_time_->start();
|
||||
}
|
||||
|
||||
void HomeWindow::stopTimer()
|
||||
{
|
||||
if(play_time_) {
|
||||
play_time_->stop();
|
||||
delete play_time_;
|
||||
play_time_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool HomeWindow::resume()
|
||||
{
|
||||
if(mp_) {
|
||||
mp_->ijkmp_start();
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool HomeWindow::pause()
|
||||
{
|
||||
if(mp_) {
|
||||
mp_->ijkmp_pause();
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_screenBtn_clicked()
|
||||
{
|
||||
if(mp_) {
|
||||
QDateTime time = QDateTime::currentDateTime();
|
||||
// 比如 20230513-161813-769.jpg
|
||||
QString dateTime = time.toString("yyyyMMdd-hhmmss-zzz") + ".jpg";
|
||||
mp_->ijkmp_screenshot((char *)dateTime.toStdString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_showTips(Toast::Level leve, QString tips)
|
||||
{
|
||||
Toast::instance().show(leve, tips);
|
||||
}
|
||||
|
||||
void HomeWindow::on_bufDurationBox_currentIndexChanged(int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0:
|
||||
max_cache_duration_ = 30;
|
||||
break;
|
||||
case 1:
|
||||
max_cache_duration_ = 100;
|
||||
break;
|
||||
case 2:
|
||||
max_cache_duration_ = 200;
|
||||
break;
|
||||
case 3:
|
||||
max_cache_duration_ = 400;
|
||||
break;
|
||||
case 4:
|
||||
max_cache_duration_ = 600;
|
||||
break;
|
||||
case 5:
|
||||
max_cache_duration_ = 800;
|
||||
break;
|
||||
case 6:
|
||||
max_cache_duration_ = 1000;
|
||||
break;
|
||||
case 7:
|
||||
max_cache_duration_ = 2000;
|
||||
break;
|
||||
case 8:
|
||||
max_cache_duration_ = 4000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_jitterBufBox_currentIndexChanged(int index)
|
||||
{
|
||||
switch (index) {
|
||||
case 0:
|
||||
max_cache_duration_ = 30;
|
||||
break;
|
||||
case 1:
|
||||
max_cache_duration_ = 100;
|
||||
break;
|
||||
case 2:
|
||||
max_cache_duration_ = 200;
|
||||
break;
|
||||
case 3:
|
||||
max_cache_duration_ = 400;
|
||||
break;
|
||||
case 4:
|
||||
max_cache_duration_ = 600;
|
||||
break;
|
||||
case 5:
|
||||
max_cache_duration_ = 800;
|
||||
break;
|
||||
case 6:
|
||||
max_cache_duration_ = 1000;
|
||||
break;
|
||||
case 7:
|
||||
max_cache_duration_ = 2000;
|
||||
break;
|
||||
case 8:
|
||||
max_cache_duration_ = 4000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_prevBtn_clicked()
|
||||
{
|
||||
// 停止当前的播放,然后播放下一个,这里就需要播放列表配合
|
||||
//获取前一个播放的url 并将对应的url选中
|
||||
std::string url = ui->playList->GetPrevUrlAndSelect();
|
||||
if(!url.empty()) {
|
||||
play(url);
|
||||
} else {
|
||||
emit sig_showTips(Toast::ERROR, "没有可以播放的URL");
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_nextBtn_clicked()
|
||||
{
|
||||
std::string url = ui->playList->GetNextUrlAndSelect();
|
||||
if(!url.empty()) {
|
||||
play(url);
|
||||
} else {
|
||||
emit sig_showTips(Toast::ERROR, "没有可以播放的URL");
|
||||
}
|
||||
}
|
||||
|
||||
void HomeWindow::on_forwardFastBtn_clicked()
|
||||
{
|
||||
fastForward(MP_SEEK_STEP);
|
||||
}
|
||||
|
||||
void HomeWindow::on_backFastBtn_clicked()
|
||||
{
|
||||
fastBack(-1 * MP_SEEK_STEP);
|
||||
}
|
||||
|
||||
void HomeWindow::on_fullBtn_clicked()
|
||||
{
|
||||
emit sig_troggleFull(true);
|
||||
}
|
||||
|
||||
146
homewindow.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#ifndef HOMEWINDOW_H
|
||||
#define HOMEWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include "toast.h"
|
||||
#include "ijkmediaplayer.h"
|
||||
namespace Ui
|
||||
{
|
||||
class HomeWindow;
|
||||
}
|
||||
|
||||
class HomeWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HomeWindow(QWidget *parent = 0);
|
||||
~HomeWindow();
|
||||
void initUi();
|
||||
int InitSignalsAndSlots();
|
||||
int message_loop(void *arg);
|
||||
int OutputVideo(const Frame *frame);
|
||||
|
||||
protected:
|
||||
virtual void resizeEvent(QResizeEvent *event);
|
||||
void resizeUI();
|
||||
void closeEvent(QCloseEvent *event);
|
||||
signals:
|
||||
// 发送要显示的提示信息
|
||||
void sig_showTips(Toast::Level leve, QString tips);
|
||||
void sig_updateAudioCacheDuration(int64_t duration);
|
||||
void sig_updateVideoCacheDuration(int64_t duration);
|
||||
void sig_updateCurrentPosition(long position);
|
||||
void sig_updatePlayOrPause(int state);
|
||||
|
||||
void sig_stopped(); // 被动停止
|
||||
void sig_troggleFull(bool);
|
||||
private slots:
|
||||
void on_UpdateAudioCacheDuration(int64_t duration);
|
||||
void on_UpdateVideoCacheDuration(int64_t duration);
|
||||
// 打开文件
|
||||
void on_openFile();
|
||||
// 打开网络流,逻辑和vlc类似
|
||||
void on_openNetworkUrl();
|
||||
void on_listBtn_clicked();
|
||||
void on_playOrPauseBtn_clicked(); // 播放暂停
|
||||
void on_updatePlayOrPause(int state);
|
||||
void on_stopBtn_clicked(); // 停止
|
||||
// stop->play
|
||||
bool play(std::string url);
|
||||
bool stop();
|
||||
|
||||
// 进度条响应
|
||||
// 拖动触发
|
||||
// void on_playSliderValueChanged();
|
||||
// void on_volumeSliderValueChanged();
|
||||
void on_updateCurrentPosition(long position);
|
||||
|
||||
void onTimeOut();
|
||||
|
||||
void on_playSliderValueChanged(int value);
|
||||
void on_volumeSliderValueChanged(int value);
|
||||
void on_speedBtn_clicked();
|
||||
|
||||
void on_screenBtn_clicked();
|
||||
void on_showTips(Toast::Level leve, QString tips);
|
||||
void on_bufDurationBox_currentIndexChanged(int index);
|
||||
|
||||
void on_jitterBufBox_currentIndexChanged(int index);
|
||||
|
||||
void on_prevBtn_clicked();
|
||||
|
||||
void on_nextBtn_clicked();
|
||||
|
||||
void on_forwardFastBtn_clicked();
|
||||
|
||||
void on_backFastBtn_clicked();
|
||||
|
||||
void on_fullBtn_clicked();
|
||||
|
||||
private:
|
||||
|
||||
void startTimer();
|
||||
void stopTimer();
|
||||
// pause->play
|
||||
bool resume();
|
||||
// play->pause
|
||||
bool pause();
|
||||
// play/pause->stop
|
||||
|
||||
void resizeCtrlBar();
|
||||
void resizeDisplayAndFileList();
|
||||
int seek(int cur_valule);
|
||||
|
||||
int fastForward(long inrc);
|
||||
int fastBack(long inrc);
|
||||
// 主动获取信息,并更新到ui
|
||||
void getTotalDuration();
|
||||
|
||||
// 定时器获取,每秒读取一次时间
|
||||
void reqUpdateCurrentPosition();
|
||||
void reqUpdateCacheDuration();
|
||||
|
||||
void initButtonIcon();
|
||||
private:
|
||||
Ui::HomeWindow *ui;
|
||||
|
||||
bool is_show_file_list_ = true; // 是否显示文件列表,默认显示
|
||||
IjkMediaPlayer *mp_ = NULL;
|
||||
|
||||
//播放相关的信息
|
||||
// 当前文件播放的总长度,单位为ms
|
||||
long total_duration_ = 0;
|
||||
long current_position_ = 0;
|
||||
int64_t pre_get_cur_pos_time_ = 0;
|
||||
|
||||
QTimer *play_time_ = nullptr;
|
||||
|
||||
int play_slider_max_value = 6000;
|
||||
bool req_seeking_ = false; //当请求seek时,中间产生的播放速度不
|
||||
|
||||
bool req_screenshot_ = false;
|
||||
|
||||
|
||||
// 缓存统计
|
||||
int max_cache_duration_ = 400; // 默认200ms
|
||||
int network_jitter_duration_ = 100; // 默认100ms
|
||||
float accelerate_speed_factor_ = 1.2; //默认加速是1.2
|
||||
float normal_speed_factor_ = 1.0; // 正常播放速度1.0
|
||||
bool is_accelerate_speed_ = false;
|
||||
|
||||
// 缓存长度
|
||||
int64_t audio_cache_duration = 0;
|
||||
int64_t video_cache_duration = 0;
|
||||
int64_t pre_get_cache_time_ = 0;
|
||||
int real_time_ = 0;
|
||||
|
||||
// 码率
|
||||
int64_t audio_bitrate_duration = 0;
|
||||
int64_t video_bitrate_duration = 0;
|
||||
|
||||
bool bIsFull;
|
||||
};
|
||||
|
||||
#endif // HOMEWINDOW_H
|
||||
558
homewindow.ui
Normal file
@@ -0,0 +1,558 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>HomeWindow</class>
|
||||
<widget class="QMainWindow" name="HomeWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1115</width>
|
||||
<height>757</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>600</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>GDMP</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QWidget" name="playwidget" native="true">
|
||||
<widget class="DisplayWind" name="display" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>160</y>
|
||||
<width>400</width>
|
||||
<height>240</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>240</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="Playlist" name="playList" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>720</x>
|
||||
<y>160</y>
|
||||
<width>240</width>
|
||||
<height>240</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>240</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>240</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="ctrlBar" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>95</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="CustomSlider" name="playSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65536</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="curPosition">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>20:10:00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>/</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="totalDuration">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>65</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>24:00:00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QToolButton" name="playOrPauseBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="stopBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="backFastBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="forwardFastBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="prevBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="nextBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="speedBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="CustomSlider" name="volumeSlider">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="ctrlrightWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="screenBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="settingBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="listBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="fullBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>64</width>
|
||||
<height>64</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menuBar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1115</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_2">
|
||||
<property name="title">
|
||||
<string>画面</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menu_3">
|
||||
<property name="title">
|
||||
<string>方向</string>
|
||||
</property>
|
||||
<addaction name="action_6"/>
|
||||
<addaction name="action_7"/>
|
||||
<addaction name="action_8"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu_4">
|
||||
<property name="title">
|
||||
<string>色调</string>
|
||||
</property>
|
||||
<addaction name="action_10"/>
|
||||
<addaction name="action_11"/>
|
||||
<addaction name="action_12"/>
|
||||
</widget>
|
||||
<addaction name="menu_3"/>
|
||||
<addaction name="menu_4"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menu">
|
||||
<property name="title">
|
||||
<string>文件</string>
|
||||
</property>
|
||||
<addaction name="openFileAction"/>
|
||||
<addaction name="openUrlAction"/>
|
||||
</widget>
|
||||
<addaction name="menu"/>
|
||||
<addaction name="menu_2"/>
|
||||
</widget>
|
||||
<action name="openFileAction">
|
||||
<property name="text">
|
||||
<string>打开文件</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_3">
|
||||
<property name="text">
|
||||
<string>反转</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_4">
|
||||
<property name="text">
|
||||
<string>暖色</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_5">
|
||||
<property name="text">
|
||||
<string>冷色</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_6">
|
||||
<property name="text">
|
||||
<string>旋转</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_7">
|
||||
<property name="text">
|
||||
<string>翻转</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_8">
|
||||
<property name="text">
|
||||
<string>正常</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_10">
|
||||
<property name="text">
|
||||
<string>暖色</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_11">
|
||||
<property name="text">
|
||||
<string>冷色</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_12">
|
||||
<property name="text">
|
||||
<string>正常</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="openUrlAction">
|
||||
<property name="text">
|
||||
<string>打开网络串流</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>DisplayWind</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">displaywind.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Playlist</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">playlist.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>CustomSlider</class>
|
||||
<extends>QSlider</extends>
|
||||
<header location="global">customslider.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
260
ijkmediaplayer.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include "ijkmediaplayer.h"
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
#include "ffmsg.h"
|
||||
#include "easylogging++.h"
|
||||
IjkMediaPlayer::IjkMediaPlayer()
|
||||
{
|
||||
LOG(INFO) << " IjkMediaPlayer()\n ";
|
||||
}
|
||||
|
||||
IjkMediaPlayer::~IjkMediaPlayer()
|
||||
{
|
||||
LOG(INFO) << " ~IjkMediaPlayer()\n ";
|
||||
// 此时需要停止线程
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_create(std::function<int (void *)> msg_loop)
|
||||
{
|
||||
int ret = 0;
|
||||
ffplayer_ = new FFPlayer();
|
||||
if(!ffplayer_) {
|
||||
LOG(INFO) << " new FFPlayer() failed\n ";
|
||||
return -1;
|
||||
}
|
||||
msg_loop_ = msg_loop;
|
||||
ret = ffplayer_->ffp_create();
|
||||
if(ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_destroy()
|
||||
{
|
||||
if(msg_thread_->joinable()) {
|
||||
LOG(INFO) << "call msg_queue_abort" ;
|
||||
msg_queue_abort(&ffplayer_->msg_queue_);
|
||||
LOG(INFO) << "wait msg loop abort" ;
|
||||
msg_thread_->join(); // 等待线程退出
|
||||
}
|
||||
|
||||
ffplayer_->ffp_destroy();
|
||||
return 0;
|
||||
}
|
||||
// 这个方法的设计来源于Android mediaplayer, 其本意是
|
||||
//int IjkMediaPlayer::ijkmp_set_data_source(Uri uri)
|
||||
int IjkMediaPlayer::ijkmp_set_data_source(const char *url)
|
||||
{
|
||||
if(!url) {
|
||||
return -1;
|
||||
}
|
||||
data_source_ = strdup(url); // 分配内存+ 拷贝字符串
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_prepare_async()
|
||||
{
|
||||
// 判断mp的状态
|
||||
// 正在准备中
|
||||
mp_state_ = MP_STATE_ASYNC_PREPARING;
|
||||
// 启用消息队列
|
||||
msg_queue_start(&ffplayer_->msg_queue_);
|
||||
// 创建循环线程
|
||||
msg_thread_ = new std::thread(&IjkMediaPlayer::ijkmp_msg_loop, this, this);
|
||||
// 调用ffplayer
|
||||
int ret = ffplayer_->ffp_prepare_async_l(data_source_);
|
||||
if(ret < 0) {
|
||||
mp_state_ = MP_STATE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_start()
|
||||
{
|
||||
ffp_notify_msg1(ffplayer_, FFP_REQ_START);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_stop()
|
||||
{
|
||||
int retval = ffplayer_->ffp_stop_l();
|
||||
if (retval < 0) {
|
||||
return retval;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_pause()
|
||||
{
|
||||
// 发送暂停的操作命令
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_START);
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_PAUSE);
|
||||
ffp_notify_msg1(ffplayer_, FFP_REQ_PAUSE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_seek_to(long msec)
|
||||
{
|
||||
seek_req = 1;
|
||||
seek_msec = msec;
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_SEEK);
|
||||
ffp_notify_msg2(ffplayer_, FFP_REQ_SEEK, (int)msec);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_forward_to(long incr)
|
||||
{
|
||||
seek_req = 1;
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_FORWARD);
|
||||
ffp_notify_msg2(ffplayer_, FFP_REQ_FORWARD, (int)incr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_back_to(long incr)
|
||||
{
|
||||
seek_req = 1;
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_FORWARD);
|
||||
ffp_notify_msg2(ffplayer_, FFP_REQ_FORWARD, (int)incr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// 请求截屏
|
||||
int IjkMediaPlayer::ijkmp_screenshot(char *file_path)
|
||||
{
|
||||
ffp_remove_msg(ffplayer_, FFP_REQ_SCREENSHOT);
|
||||
ffp_notify_msg4(ffplayer_, FFP_REQ_SCREENSHOT, 0, 0, file_path, strlen(file_path) + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_get_state()
|
||||
{
|
||||
return mp_state_;
|
||||
}
|
||||
|
||||
long IjkMediaPlayer::ijkmp_get_current_position()
|
||||
{
|
||||
return ffplayer_->ffp_get_current_position_l();
|
||||
}
|
||||
|
||||
long IjkMediaPlayer::ijkmp_get_duration()
|
||||
{
|
||||
return ffplayer_->ffp_get_duration_l();
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_get_msg(AVMessage *msg, int block)
|
||||
{
|
||||
int pause_ret = 0;
|
||||
while (1) {
|
||||
int continue_wait_next_msg = 0;
|
||||
//取消息,没有消息则根据block值 =1阻塞,=0不阻塞。
|
||||
int retval = msg_queue_get(&ffplayer_->msg_queue_, msg, block);
|
||||
if (retval <= 0) { // -1 abort, 0 没有消息
|
||||
return retval;
|
||||
}
|
||||
switch (msg->what) {
|
||||
case FFP_MSG_PREPARED:
|
||||
LOG(INFO) << " FFP_MSG_PREPARED" ;
|
||||
// ijkmp_change_state_l(MP_STATE_PREPARED);
|
||||
break;
|
||||
case FFP_REQ_START:
|
||||
LOG(INFO) << " FFP_REQ_START" ;
|
||||
continue_wait_next_msg = 1;
|
||||
retval = ffplayer_->ffp_start_l();
|
||||
if (retval == 0) {
|
||||
ijkmp_change_state_l(MP_STATE_STARTED);
|
||||
}
|
||||
break;
|
||||
case FFP_REQ_PAUSE:
|
||||
continue_wait_next_msg = 1;
|
||||
pause_ret = ffplayer_->ffp_pause_l();
|
||||
if(pause_ret == 0) {
|
||||
//设置为暂停暂停
|
||||
ijkmp_change_state_l(MP_STATE_PAUSED); // 暂停后怎么恢复?
|
||||
}
|
||||
break;
|
||||
case FFP_MSG_SEEK_COMPLETE:
|
||||
LOG(INFO) << "ijkmp_get_msg: FFP_MSG_SEEK_COMPLETE\n";
|
||||
seek_req = 0;
|
||||
seek_msec = 0;
|
||||
break;
|
||||
case FFP_REQ_SEEK:
|
||||
LOG(INFO) << "ijkmp_get_msg: FFP_REQ_SEEK\n";
|
||||
continue_wait_next_msg = 1;
|
||||
ffplayer_->ffp_seek_to_l(msg->arg1);
|
||||
break;
|
||||
case FFP_REQ_FORWARD:
|
||||
LOG(INFO) << "ijkmp_get_msg: FFP_REQ_FORWARD\n";
|
||||
continue_wait_next_msg = 1;
|
||||
ffplayer_->ffp_forward_to_l(msg->arg1);
|
||||
break;
|
||||
case FFP_REQ_BACK:
|
||||
LOG(INFO) << "ijkmp_get_msg: FFP_REQ_BACK\n";
|
||||
continue_wait_next_msg = 1;
|
||||
ffplayer_->ffp_back_to_l(msg->arg1);
|
||||
break;
|
||||
case FFP_REQ_SCREENSHOT:
|
||||
LOG(INFO) << "ijkmp_get_msg: FFP_REQ_SCREENSHOT: " << (char *)msg->obj ;
|
||||
continue_wait_next_msg = 1;
|
||||
ffplayer_->ffp_screenshot_l((char *)msg->obj);
|
||||
break;
|
||||
default:
|
||||
LOG(INFO) << " default " << msg->what ;
|
||||
break;
|
||||
}
|
||||
if (continue_wait_next_msg) {
|
||||
msg_free_res(msg);
|
||||
continue;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void IjkMediaPlayer::ijkmp_set_playback_volume(int volume)
|
||||
{
|
||||
ffplayer_->ffp_set_playback_volume(volume);
|
||||
}
|
||||
|
||||
int IjkMediaPlayer::ijkmp_msg_loop(void *arg)
|
||||
{
|
||||
msg_loop_(arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IjkMediaPlayer::ijkmp_set_playback_rate(float rate)
|
||||
{
|
||||
ffplayer_->ffp_set_playback_rate(rate);
|
||||
}
|
||||
|
||||
float IjkMediaPlayer::ijkmp_get_playback_rate()
|
||||
{
|
||||
return ffplayer_->ffp_get_playback_rate();
|
||||
}
|
||||
|
||||
void IjkMediaPlayer::AddVideoRefreshCallback(
|
||||
std::function<int (const Frame *)> callback)
|
||||
{
|
||||
ffplayer_->AddVideoRefreshCallback(callback);
|
||||
}
|
||||
|
||||
int64_t IjkMediaPlayer::ijkmp_get_property_int64(int id, int64_t default_value)
|
||||
{
|
||||
ffplayer_->ffp_get_property_int64(id, default_value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IjkMediaPlayer::ijkmp_change_state_l(int new_state)
|
||||
{
|
||||
mp_state_ = new_state;
|
||||
ffp_notify_msg1(ffplayer_, FFP_MSG_PLAYBACK_STATE_CHANGED);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
193
ijkmediaplayer.h
Normal file
@@ -0,0 +1,193 @@
|
||||
#ifndef IJKMEDIAPLAYER_H
|
||||
#define IJKMEDIAPLAYER_H
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <functional>
|
||||
#include "ff_ffplay_def.h"
|
||||
#include "ff_ffplay.h"
|
||||
#include "ffmsg_queue.h"
|
||||
|
||||
/*-
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_IDLE);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_INITIALIZED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_ASYNC_PREPARING);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_PREPARED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_STARTED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_PAUSED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_COMPLETED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_STOPPED);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_ERROR);
|
||||
MPST_CHECK_NOT_RET(mp->mp_state, MP_STATE_END);
|
||||
*/
|
||||
|
||||
/*-
|
||||
* ijkmp_set_data_source() -> MP_STATE_INITIALIZED
|
||||
*
|
||||
* ijkmp_reset -> self
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_IDLE 0
|
||||
|
||||
/*-
|
||||
* ijkmp_prepare_async() -> MP_STATE_ASYNC_PREPARING
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_INITIALIZED 1
|
||||
|
||||
/*-
|
||||
* ... -> MP_STATE_PREPARED
|
||||
* ... -> MP_STATE_ERROR
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_ASYNC_PREPARING 2
|
||||
|
||||
/*-
|
||||
* ijkmp_seek_to() -> self
|
||||
* ijkmp_start() -> MP_STATE_STARTED
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_PREPARED 3
|
||||
|
||||
/*-
|
||||
* ijkmp_seek_to() -> self
|
||||
* ijkmp_start() -> self
|
||||
* ijkmp_pause() -> MP_STATE_PAUSED
|
||||
* ijkmp_stop() -> MP_STATE_STOPPED
|
||||
* ... -> MP_STATE_COMPLETED
|
||||
* ... -> MP_STATE_ERROR
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_STARTED 4
|
||||
|
||||
/*-
|
||||
* ijkmp_seek_to() -> self
|
||||
* ijkmp_start() -> MP_STATE_STARTED
|
||||
* ijkmp_pause() -> self
|
||||
* ijkmp_stop() -> MP_STATE_STOPPED
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_PAUSED 5
|
||||
|
||||
/*-
|
||||
* ijkmp_seek_to() -> self
|
||||
* ijkmp_start() -> MP_STATE_STARTED (from beginning)
|
||||
* ijkmp_pause() -> self
|
||||
* ijkmp_stop() -> MP_STATE_STOPPED
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_COMPLETED 6
|
||||
|
||||
/*-
|
||||
* ijkmp_stop() -> self
|
||||
* ijkmp_prepare_async() -> MP_STATE_ASYNC_PREPARING
|
||||
*
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_STOPPED 7
|
||||
|
||||
/*-
|
||||
* ijkmp_reset -> MP_STATE_IDLE
|
||||
* ijkmp_release -> MP_STATE_END
|
||||
*/
|
||||
#define MP_STATE_ERROR 8
|
||||
|
||||
/*-
|
||||
* ijkmp_release -> self
|
||||
*/
|
||||
#define MP_STATE_END 9
|
||||
|
||||
#define MP_SEEK_STEP 10 // 快退快进步长10秒
|
||||
|
||||
|
||||
class IjkMediaPlayer
|
||||
{
|
||||
public:
|
||||
IjkMediaPlayer();
|
||||
~IjkMediaPlayer();
|
||||
int ijkmp_create(std::function<int(void *)> msg_loop);
|
||||
int ijkmp_destroy();
|
||||
// 设置要播放的url
|
||||
int ijkmp_set_data_source(const char *url);
|
||||
// 准备播放
|
||||
int ijkmp_prepare_async();
|
||||
// 触发播放
|
||||
int ijkmp_start();
|
||||
// 停止
|
||||
int ijkmp_stop();
|
||||
// 暂停
|
||||
int ijkmp_pause();
|
||||
// seek到指定位置
|
||||
int ijkmp_seek_to(long msec);
|
||||
// 快进
|
||||
int ijkmp_forward_to(long incr);
|
||||
// 快退
|
||||
int ijkmp_back_to(long incr);
|
||||
|
||||
int ijkmp_screenshot(char *file_path);
|
||||
// 获取播放状态
|
||||
int ijkmp_get_state();
|
||||
// 是不是播放中
|
||||
bool ijkmp_is_playing();
|
||||
// 当前播放位置
|
||||
long ijkmp_get_current_position();
|
||||
// 总长度
|
||||
long ijkmp_get_duration();
|
||||
// 已经播放的长度
|
||||
long ijkmp_get_playable_duration();
|
||||
// 设置循环播放
|
||||
void ijkmp_set_loop(int loop);
|
||||
// 获取是否循环播放
|
||||
int ijkmp_get_loop();
|
||||
// 读取消息
|
||||
int ijkmp_get_msg(AVMessage *msg, int block);
|
||||
// 设置音量
|
||||
void ijkmp_set_playback_volume(int volume);
|
||||
|
||||
int ijkmp_msg_loop(void *arg);
|
||||
|
||||
void ijkmp_set_playback_rate(float rate);
|
||||
float ijkmp_get_playback_rate();
|
||||
void AddVideoRefreshCallback(std::function<int(const Frame *)> callback);
|
||||
|
||||
// 获取状态值
|
||||
int64_t ijkmp_get_property_int64(int id, int64_t default_value);
|
||||
|
||||
void ijkmp_change_state_l(int new_state);
|
||||
private:
|
||||
// 互斥量
|
||||
std::mutex mutex_;
|
||||
// 真正的播放器
|
||||
FFPlayer *ffplayer_ = NULL;
|
||||
//函数指针, 指向创建的message_loop,即消息循环函数
|
||||
// int (*msg_loop)(void*);
|
||||
std::function<int(void *)> msg_loop_ = NULL; // ui处理消息的循环
|
||||
//消息机制线程
|
||||
std::thread *msg_thread_; // 执行msg_loop
|
||||
// SDL_Thread _msg_thread;
|
||||
//字符串,就是一个播放url
|
||||
char *data_source_;
|
||||
//播放器状态,例如prepared,resumed,error,completed等
|
||||
int mp_state_; // 播放状态
|
||||
|
||||
int seek_req = 0;
|
||||
long seek_msec = 0;
|
||||
|
||||
// 截屏请求
|
||||
char *file_path_ = NULL;
|
||||
};
|
||||
|
||||
#endif // IJKMEDIAPLAYER_H
|
||||
6
ijksdl_timer.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "ijksdl_timer.h"
|
||||
|
||||
ijksdl_timer::ijksdl_timer()
|
||||
{
|
||||
|
||||
}
|
||||
20
ijksdl_timer.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#ifndef IJKSDL_TIMER_H
|
||||
#define IJKSDL_TIMER_H
|
||||
|
||||
#include <stdint.h>
|
||||
typedef struct SDL_SpeedSampler2
|
||||
{
|
||||
int64_t sample_range;
|
||||
int64_t last_profile_tick;
|
||||
int64_t last_profile_duration;
|
||||
int64_t last_profile_quantity;
|
||||
int64_t last_profile_speed;
|
||||
} SDL_SpeedSampler2;
|
||||
|
||||
class ijksdl_timer
|
||||
{
|
||||
public:
|
||||
ijksdl_timer();
|
||||
};
|
||||
|
||||
#endif // IJKSDL_TIMER_H
|
||||
183
imagescaler.h
Normal file
@@ -0,0 +1,183 @@
|
||||
#ifndef IMAGESCALER_H
|
||||
#define IMAGESCALER_H
|
||||
#include "ijkmediaplayer.h"
|
||||
|
||||
#include "easylogging++.h"
|
||||
//Scale算法
|
||||
enum SwsAlogrithm
|
||||
{
|
||||
SWS_SA_FAST_BILINEAR = 0x1,
|
||||
SWS_SA_BILINEAR = 0x2,
|
||||
SWS_SA_BICUBIC = 0x4,
|
||||
SWS_SA_X = 0x8,
|
||||
SWS_SA_POINT = 0x10,
|
||||
SWS_SA_AREA = 0x20,
|
||||
SWS_SA_BICUBLIN = 0x40,
|
||||
SWS_SA_GAUSS = 0x80,
|
||||
SWS_SA_SINC = 0x100,
|
||||
SWS_SA_LANCZOS = 0x200,
|
||||
SWS_SA_SPLINE = 0x400,
|
||||
};
|
||||
|
||||
typedef struct VideoFrame
|
||||
{
|
||||
uint8_t *data[8] = {NULL}; // 类似FFmpeg的buf, 如果是
|
||||
int32_t linesize[8] = {0};
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
int format = AV_PIX_FMT_YUV420P;
|
||||
}VideoFrame;
|
||||
|
||||
class ImageScaler
|
||||
{
|
||||
public:
|
||||
ImageScaler(void) {
|
||||
sws_ctx_ = NULL;
|
||||
src_pix_fmt_ = AV_PIX_FMT_NONE;
|
||||
dst_pix_fmt_ = AV_PIX_FMT_NONE;
|
||||
en_alogrithm_ = SWS_SA_FAST_BILINEAR;
|
||||
|
||||
src_width_ = src_height_ = 0;
|
||||
dst_width_ = dst_height_ = 0;
|
||||
}
|
||||
~ImageScaler(void) {
|
||||
DeInit();
|
||||
}
|
||||
RET_CODE Init(uint32_t src_width, uint32_t src_height, int src_pix_fmt,
|
||||
uint32_t dst_width, uint32_t dst_height, int dst_pix_fmt,
|
||||
int en_alogrithm = SWS_SA_FAST_BILINEAR) {
|
||||
src_width_ = src_width;
|
||||
src_height_ = src_height;
|
||||
src_pix_fmt_ = (AVPixelFormat)src_pix_fmt;
|
||||
dst_width_ = dst_width;
|
||||
dst_height_ = dst_height;
|
||||
dst_pix_fmt_ = (AVPixelFormat)dst_pix_fmt;
|
||||
en_alogrithm_ = en_alogrithm;
|
||||
sws_ctx_ = sws_getContext(
|
||||
src_width_,
|
||||
src_height_,
|
||||
(AVPixelFormat)src_pix_fmt_,
|
||||
dst_width_,
|
||||
dst_height_,
|
||||
(AVPixelFormat)dst_pix_fmt_,
|
||||
SWS_FAST_BILINEAR,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
if (!sws_ctx_) {
|
||||
LOG(ERROR) << "Impossible to create scale context for the conversion fmt:"
|
||||
<< av_get_pix_fmt_name((enum AVPixelFormat)src_pix_fmt_)
|
||||
<< ", s:" << src_width_ << "x" << src_height_ << " -> fmt:" << av_get_pix_fmt_name(dst_pix_fmt_)
|
||||
<< ", s:" << dst_width_ << "x" << dst_height_;
|
||||
return RET_FAIL;
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
void DeInit( ) {
|
||||
if(sws_ctx_) {
|
||||
sws_freeContext(sws_ctx_);
|
||||
sws_ctx_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RET_CODE Scale(const AVFrame *src_frame, AVFrame *dst_frame) {
|
||||
if(src_frame->width != src_width_
|
||||
|| src_frame->height != src_height_
|
||||
|| src_frame->format != src_pix_fmt_
|
||||
|| dst_frame->width != dst_width_
|
||||
|| dst_frame->height != dst_height_
|
||||
|| dst_frame->format != dst_pix_fmt_
|
||||
|| !sws_ctx_) {
|
||||
// 重新初始化
|
||||
DeInit();
|
||||
RET_CODE ret = Init(src_frame->width, src_frame->height, src_frame->format,
|
||||
dst_frame->width, dst_frame->height, dst_frame->format,
|
||||
en_alogrithm_);
|
||||
if(ret != RET_OK) {
|
||||
LOG(ERROR) << "Init failed: " << ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int dst_slice_h = sws_scale(sws_ctx_, (const uint8_t **) src_frame->data, src_frame->linesize, 0, src_frame->height,
|
||||
dst_frame->data, dst_frame->linesize);
|
||||
if(dst_slice_h>0)
|
||||
return RET_OK;
|
||||
else
|
||||
return RET_FAIL;
|
||||
}
|
||||
|
||||
RET_CODE Scale2(const VideoFrame *src_frame, VideoFrame *dst_frame) {
|
||||
if(src_frame->width != src_width_
|
||||
|| src_frame->height != src_height_
|
||||
|| src_frame->format != src_pix_fmt_
|
||||
|| dst_frame->width != dst_width_
|
||||
|| dst_frame->height != dst_height_
|
||||
|| dst_frame->format != dst_pix_fmt_
|
||||
|| !sws_ctx_) {
|
||||
DeInit();
|
||||
RET_CODE ret = Init(src_frame->width, src_frame->height, src_frame->format,
|
||||
dst_frame->width, dst_frame->height, dst_frame->format,
|
||||
en_alogrithm_);
|
||||
if(ret != RET_OK) {
|
||||
LOG(ERROR) << "Init failed: " << ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
int dst_slice_h = sws_scale(sws_ctx_,
|
||||
(const uint8_t **)src_frame->data,
|
||||
src_frame->linesize,
|
||||
0, // 起始位置
|
||||
src_frame->height, //处理多少行
|
||||
dst_frame->data,
|
||||
dst_frame->linesize);
|
||||
if(dst_slice_h>0)
|
||||
return RET_OK;
|
||||
else
|
||||
return RET_FAIL;
|
||||
}
|
||||
RET_CODE Scale3(const Frame *src_frame, VideoFrame *dst_frame) {
|
||||
if(src_frame->width != src_width_
|
||||
|| src_frame->height != src_height_
|
||||
|| src_frame->format != src_pix_fmt_
|
||||
|| dst_frame->width != dst_width_
|
||||
|| dst_frame->height != dst_height_
|
||||
|| dst_frame->format != dst_pix_fmt_
|
||||
|| !sws_ctx_) {
|
||||
DeInit();
|
||||
RET_CODE ret = Init(src_frame->width, src_frame->height, src_frame->format,
|
||||
dst_frame->width, dst_frame->height, dst_frame->format,
|
||||
en_alogrithm_);
|
||||
if(ret != RET_OK) {
|
||||
LOG(ERROR) << "Init failed: " << ret;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
int dst_slice_h = sws_scale(sws_ctx_,
|
||||
(const uint8_t **)src_frame->frame->data,
|
||||
src_frame->frame->linesize,
|
||||
0, // 起始位置
|
||||
src_frame->height, //处理多少行
|
||||
dst_frame->data,
|
||||
dst_frame->linesize);
|
||||
if(dst_slice_h>0)
|
||||
return RET_OK;
|
||||
else
|
||||
return RET_FAIL;
|
||||
}
|
||||
private:
|
||||
|
||||
SwsContext* sws_ctx_; //SWS对象
|
||||
AVPixelFormat src_pix_fmt_; //源像素格式
|
||||
AVPixelFormat dst_pix_fmt_; //目标像素格式
|
||||
int en_alogrithm_ = SWS_SA_FAST_BILINEAR; //Resize算法
|
||||
|
||||
int src_width_, src_height_; //源图像宽高
|
||||
|
||||
int dst_width_, dst_height_; //目标图像宽高
|
||||
|
||||
};
|
||||
|
||||
#endif // IMAGESCALER_H
|
||||
3120
log/easylogging++.cc
Normal file
4576
log/easylogging++.h
Normal file
33
main.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "homewindow.h"
|
||||
#include <QApplication>
|
||||
#include "easylogging++.h"
|
||||
|
||||
INITIALIZE_EASYLOGGINGPP // 初始化宏,有且只能使用一次
|
||||
|
||||
#undef main
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
// el::Loggers::reconfigureAllLoggers(el::ConfigurationType::Format, "%datetime %level %func(L%line) %msg");
|
||||
|
||||
el::Configurations conf;
|
||||
conf.setToDefault();
|
||||
conf.setGlobally(el::ConfigurationType::Format, "[%datetime | %level] %func(L%line) %msg");
|
||||
conf.setGlobally(el::ConfigurationType::Filename, "log_%datetime{%Y%M%d}.log");
|
||||
conf.setGlobally(el::ConfigurationType::Enabled, "true");
|
||||
conf.setGlobally(el::ConfigurationType::ToFile, "true");
|
||||
el::Loggers::reconfigureAllLoggers(conf);
|
||||
el::Loggers::reconfigureAllLoggers(el::ConfigurationType::ToStandardOutput, "true"); // 也输出一份到终端
|
||||
|
||||
// LOG(VERBOSE) << "logger test"; //该级别只能用宏VLOG而不能用宏 LOG(VERBOSE)
|
||||
LOG(TRACE) << " logger";
|
||||
// LOG(DEBUG) << "logger test";
|
||||
LOG(INFO) << "logger test";
|
||||
LOG(WARNING) << "logger test";
|
||||
LOG(ERROR) << "logger test";
|
||||
QApplication a(argc, argv);
|
||||
HomeWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
77
medialist.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <QContextMenuEvent>
|
||||
#include <QFileDialog>
|
||||
#include "medialist.h"
|
||||
#include "urldialog.h"
|
||||
#include "easylogging++.h"
|
||||
//#include <QDebug>
|
||||
//#define LOG(INFO) qDebug()
|
||||
#pragma execution_character_set("utf-8")
|
||||
|
||||
MediaList::MediaList(QWidget *parent)
|
||||
: QListWidget(parent),
|
||||
menu_(this),
|
||||
m_stActAddFile(this),
|
||||
m_stActAddUrl(this),
|
||||
m_stActRemove(this),
|
||||
m_stActClearList(this)
|
||||
{
|
||||
}
|
||||
|
||||
MediaList::~MediaList()
|
||||
{
|
||||
}
|
||||
|
||||
bool MediaList::Init()
|
||||
{
|
||||
m_stActAddFile.setText("添加文件");
|
||||
menu_.addAction(&m_stActAddFile);
|
||||
m_stActAddUrl.setText("添加地址");
|
||||
menu_.addAction(&m_stActAddUrl);
|
||||
m_stActRemove.setText("移除");
|
||||
menu_.addAction(&m_stActRemove);
|
||||
m_stActClearList.setText("清空列表");
|
||||
menu_.addAction(&m_stActClearList);
|
||||
connect(&m_stActAddFile, &QAction::triggered, this, &MediaList::AddFile);
|
||||
connect(&m_stActAddUrl, &QAction::triggered, this, &MediaList::AddUrl);
|
||||
connect(&m_stActRemove, &QAction::triggered, this, &MediaList::RemoveFile);
|
||||
connect(&m_stActClearList, &QAction::triggered, this, &QListWidget::clear);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MediaList::contextMenuEvent(QContextMenuEvent* event)
|
||||
{
|
||||
menu_.exec(event->globalPos());
|
||||
}
|
||||
|
||||
void MediaList::AddFile()
|
||||
{
|
||||
QStringList listFileName = QFileDialog::getOpenFileNames(this, "打开文件", QDir::homePath(),
|
||||
"视频文件(*.ts *.mkv *.rmvb *.mp4 *.avi *.flv *.wmv *.3gp *.wav *.mp3 *.aac)");
|
||||
for (QString strFileName : listFileName) {
|
||||
emit SigAddFile(strFileName);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaList::AddUrl()
|
||||
{
|
||||
UrlDialog urlDialog(this);
|
||||
int nResult = urlDialog.exec();
|
||||
if(nResult == QDialog::Accepted) {
|
||||
//
|
||||
QString url = urlDialog.GetUrl();
|
||||
LOG(INFO) << "Add url ok, url: " << url.toStdString();
|
||||
if(!url.isEmpty()) {
|
||||
LOG(INFO) << "SigAddFile url: " << url.toStdString();
|
||||
emit SigAddFile(url);
|
||||
} else {
|
||||
LOG(ERROR) << "Add url no";
|
||||
}
|
||||
} else {
|
||||
LOG(WARNING) << "Add url Rejected";
|
||||
}
|
||||
}
|
||||
|
||||
void MediaList::RemoveFile()
|
||||
{
|
||||
takeItem(currentRow());
|
||||
}
|
||||
35
medialist.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef MEDIALIST_H
|
||||
#define MEDIALIST_H
|
||||
|
||||
|
||||
#include <QListWidget>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
class MediaList : public QListWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MediaList(QWidget *parent = 0);
|
||||
~MediaList();
|
||||
bool Init();
|
||||
protected:
|
||||
void contextMenuEvent(QContextMenuEvent* event);
|
||||
public:
|
||||
void AddFile(); //添加文件
|
||||
void AddUrl(); // 添加网络地址
|
||||
void RemoveFile();
|
||||
signals:
|
||||
void SigAddFile(QString strFileName); //添加文件信号
|
||||
|
||||
|
||||
private:
|
||||
QMenu menu_;
|
||||
|
||||
QAction m_stActAddFile; //添加文件
|
||||
QAction m_stActAddUrl; // 添加网络URL
|
||||
QAction m_stActRemove; //移除文件
|
||||
QAction m_stActClearList; //清空列表
|
||||
};
|
||||
|
||||
#endif // MEDIALIST_H
|
||||
BIN
player.ico
Normal file
|
After Width: | Height: | Size: 162 KiB |
275
playlist.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include <QDir>
|
||||
|
||||
#include "playlist.h"
|
||||
#include "ui_playlist.h"
|
||||
|
||||
#include "globalhelper.h"
|
||||
|
||||
#include "easylogging++.h"
|
||||
|
||||
Playlist::Playlist(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::Playlist)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
Playlist::~Playlist()
|
||||
{
|
||||
savePlayList();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool Playlist::Init()
|
||||
{
|
||||
if (ui->List->Init() == false) {
|
||||
return false;
|
||||
}
|
||||
if (InitUi() == false) {
|
||||
return false;
|
||||
}
|
||||
if (ConnectSignalSlots() == false) {
|
||||
return false;
|
||||
}
|
||||
setAcceptDrops(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Playlist::InitUi()
|
||||
{
|
||||
// setStyleSheet(GlobalHelper::GetQssStr("://res/qss/playlist.css"));
|
||||
//ui->List->hide();
|
||||
//this->setFixedWidth(ui->HideOrShowBtn->width());
|
||||
//GlobalHelper::SetIcon(ui->HideOrShowBtn, 12, QChar(0xf104));
|
||||
ui->List->clear();
|
||||
QStringList strListPlaylist;
|
||||
GlobalHelper::GetPlaylist(strListPlaylist);
|
||||
for (QString strVideoFile : strListPlaylist) {
|
||||
QFileInfo fileInfo(strVideoFile);
|
||||
// if (fileInfo.exists())
|
||||
{
|
||||
QListWidgetItem *pItem = new QListWidgetItem(ui->List);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(QString("%1").arg(fileInfo.fileName())); // 显示文本
|
||||
pItem->setToolTip(fileInfo.filePath());
|
||||
ui->List->addItem(pItem);
|
||||
}
|
||||
}
|
||||
if (strListPlaylist.length() > 0) {
|
||||
ui->List->setCurrentRow(0);
|
||||
}
|
||||
//ui->List->addItems(strListPlaylist);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Playlist::ConnectSignalSlots()
|
||||
{
|
||||
QList<bool> listRet;
|
||||
bool bRet;
|
||||
bRet = connect(ui->List, &MediaList::SigAddFile, this, &Playlist::OnAddFile);
|
||||
listRet.append(bRet);
|
||||
for (bool bReturn : listRet) {
|
||||
if (bReturn == false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Playlist::savePlayList()
|
||||
{
|
||||
QStringList strListPlayList;
|
||||
for (int i = 0; i < ui->List->count(); i++) {
|
||||
strListPlayList.append(ui->List->item(i)->toolTip());
|
||||
}
|
||||
GlobalHelper::SavePlaylist(strListPlayList);
|
||||
}
|
||||
|
||||
void Playlist::on_List_itemDoubleClicked(QListWidgetItem *item)
|
||||
{
|
||||
LOG(INFO) << "play list double click: " << item->data(Qt::UserRole).toString().toStdString();
|
||||
emit SigPlay(item->data(Qt::UserRole).toString().toStdString());
|
||||
m_nCurrentPlayListIndex = ui->List->row(item);
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
|
||||
bool Playlist::GetPlaylistStatus()
|
||||
{
|
||||
if (this->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int Playlist::GetCurrentIndex()
|
||||
{
|
||||
return m_nCurrentPlayListIndex;
|
||||
}
|
||||
|
||||
std::string Playlist::GetCurrentUrl()
|
||||
{
|
||||
std::string url;
|
||||
if(ui->List->count() > 0) {
|
||||
QListWidgetItem *item = ui->List->item(m_nCurrentPlayListIndex);
|
||||
url = item->data(Qt::UserRole).toString().toStdString();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string Playlist::GetPrevUrlAndSelect()
|
||||
{
|
||||
std::string url;
|
||||
if(ui->List->count() > 0) {
|
||||
m_nCurrentPlayListIndex -= 1;
|
||||
if(m_nCurrentPlayListIndex < 0) {
|
||||
m_nCurrentPlayListIndex = ui->List->count() - 1;
|
||||
}
|
||||
QListWidgetItem *item = ui->List->item(m_nCurrentPlayListIndex);
|
||||
url = item->data(Qt::UserRole).toString().toStdString();
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex); // 选中当前行
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
std::string Playlist::GetNextUrlAndSelect()
|
||||
{
|
||||
std::string url;
|
||||
if(ui->List->count() > 0) {
|
||||
m_nCurrentPlayListIndex += 1;
|
||||
if(m_nCurrentPlayListIndex >= ui->List->count()) {
|
||||
m_nCurrentPlayListIndex = 0;
|
||||
}
|
||||
QListWidgetItem *item = ui->List->item(m_nCurrentPlayListIndex);
|
||||
url = item->data(Qt::UserRole).toString().toStdString();
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex); // 选中当前行
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
void Playlist::AddNetworkUrl(QString network_url)
|
||||
{
|
||||
OnAddFile(network_url);
|
||||
}
|
||||
|
||||
void Playlist::OnRequestPlayCurrentFile()
|
||||
{
|
||||
if(ui->List->count() > 0) { // 有文件才会触发请求播放
|
||||
on_List_itemDoubleClicked(ui->List->item(m_nCurrentPlayListIndex));
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex); // 选中当前行
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::OnAddFile(QString strFileName)
|
||||
{
|
||||
bool bSupportMovie = strFileName.endsWith(".mkv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".rmvb", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".mp4", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".avi", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".flv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".wmv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".ts", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".3gp", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".wav", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".mp3", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".aac", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("rtp://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("udp://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("rtmp://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("https://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("http://", Qt::CaseInsensitive);
|
||||
if (!bSupportMovie) {
|
||||
return;
|
||||
}
|
||||
QFileInfo fileInfo(strFileName);
|
||||
QList<QListWidgetItem *> listItem = ui->List->findItems(fileInfo.fileName(), Qt::MatchExactly);
|
||||
QListWidgetItem *pItem = nullptr;
|
||||
if (listItem.isEmpty()) {
|
||||
pItem = new QListWidgetItem(ui->List);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(fileInfo.fileName()); // 显示文件名
|
||||
pItem->setToolTip(fileInfo.filePath()); // 完整文件路径
|
||||
ui->List->addItem(pItem);
|
||||
// 加入成功则选中行数
|
||||
} else {
|
||||
pItem = listItem.at(0);
|
||||
}
|
||||
m_nCurrentPlayListIndex = ui->List->row(pItem);
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex); // ui选中状态更新为该url
|
||||
savePlayList();
|
||||
}
|
||||
|
||||
void Playlist::OnAddFileAndPlay(QString strFileName)
|
||||
{
|
||||
bool bSupportMovie = strFileName.endsWith(".mkv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".rmvb", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".mp4", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".avi", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".flv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".wmv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".3gp", Qt::CaseInsensitive);
|
||||
if (!bSupportMovie) {
|
||||
return;
|
||||
}
|
||||
QFileInfo fileInfo(strFileName);
|
||||
QList<QListWidgetItem *> listItem = ui->List->findItems(fileInfo.fileName(), Qt::MatchExactly);
|
||||
QListWidgetItem *pItem = nullptr;
|
||||
if (listItem.isEmpty()) {
|
||||
pItem = new QListWidgetItem(ui->List);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(fileInfo.fileName()); // 显示文本
|
||||
pItem->setToolTip(fileInfo.filePath());
|
||||
ui->List->addItem(pItem);
|
||||
} else {
|
||||
pItem = listItem.at(0);
|
||||
}
|
||||
on_List_itemDoubleClicked(pItem);
|
||||
savePlayList();
|
||||
}
|
||||
|
||||
void Playlist::OnBackwardPlay()
|
||||
{
|
||||
if (m_nCurrentPlayListIndex == 0) {
|
||||
m_nCurrentPlayListIndex = ui->List->count() - 1;
|
||||
on_List_itemDoubleClicked(ui->List->item(m_nCurrentPlayListIndex));
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
} else {
|
||||
m_nCurrentPlayListIndex--;
|
||||
on_List_itemDoubleClicked(ui->List->item(m_nCurrentPlayListIndex));
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::OnForwardPlay()
|
||||
{
|
||||
if (m_nCurrentPlayListIndex == ui->List->count() - 1) {
|
||||
m_nCurrentPlayListIndex = 0;
|
||||
on_List_itemDoubleClicked(ui->List->item(m_nCurrentPlayListIndex));
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
} else {
|
||||
m_nCurrentPlayListIndex++;
|
||||
on_List_itemDoubleClicked(ui->List->item(m_nCurrentPlayListIndex));
|
||||
ui->List->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::dropEvent(QDropEvent *event)
|
||||
{
|
||||
QList<QUrl> urls = event->mimeData()->urls();
|
||||
if (urls.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (QUrl url : urls) {
|
||||
QString strFileName = url.toLocalFile();
|
||||
OnAddFile(strFileName);
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void Playlist::on_List_itemSelectionChanged()
|
||||
{
|
||||
m_nCurrentPlayListIndex = ui->List->currentIndex().row(); // 获取选中后的新位置。
|
||||
}
|
||||
103
playlist.h
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* @file playlist.h
|
||||
* @date 2018/01/07 11:12
|
||||
*
|
||||
* @author itisyang
|
||||
* @Contact itisyang@gmail.com
|
||||
*
|
||||
* @brief 播放列表控件
|
||||
* @note
|
||||
*/
|
||||
#ifndef PLAYLIST_H
|
||||
#define PLAYLIST_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QDropEvent>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QMimeData>
|
||||
|
||||
namespace Ui {
|
||||
class Playlist;
|
||||
}
|
||||
|
||||
class Playlist : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Playlist(QWidget *parent = 0);
|
||||
~Playlist();
|
||||
|
||||
bool Init();
|
||||
|
||||
|
||||
/**
|
||||
* @brief 获取播放列表状态
|
||||
*
|
||||
* @return true 显示 false 隐藏
|
||||
* @note
|
||||
*/
|
||||
bool GetPlaylistStatus();
|
||||
int GetCurrentIndex();
|
||||
std::string GetCurrentUrl();
|
||||
// 获取前一个url并将前一个url设置为选中状态
|
||||
std::string GetPrevUrlAndSelect();
|
||||
// 获取下一个url并将下一个url设置为选中状态
|
||||
std::string GetNextUrlAndSelect();
|
||||
public:
|
||||
void AddNetworkUrl(QString network_url);
|
||||
/**
|
||||
* @brief 添加文件
|
||||
*
|
||||
* @param strFileName 文件完整路径
|
||||
* @note
|
||||
*/
|
||||
void OnAddFile(QString strFileName);
|
||||
void OnAddFileAndPlay(QString strFileName);
|
||||
|
||||
void OnBackwardPlay();
|
||||
void OnForwardPlay();
|
||||
void OnRequestPlayCurrentFile();
|
||||
/* 在这里定义dock的初始大小 */
|
||||
QSize sizeHint() const
|
||||
{
|
||||
return QSize(150, 900);
|
||||
}
|
||||
protected:
|
||||
/**
|
||||
* @brief 放下事件
|
||||
*
|
||||
* @param event 事件指针
|
||||
* @note
|
||||
*/
|
||||
void dropEvent(QDropEvent *event);
|
||||
/**
|
||||
* @brief 拖动事件
|
||||
*
|
||||
* @param event 事件指针
|
||||
* @note
|
||||
*/
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
|
||||
signals:
|
||||
void SigUpdateUi(); //< 界面排布更新
|
||||
void SigPlay(std::string url); //< 播放文件
|
||||
|
||||
private:
|
||||
bool InitUi();
|
||||
bool ConnectSignalSlots();
|
||||
void savePlayList();
|
||||
private slots:
|
||||
// 双击事件响应
|
||||
void on_List_itemDoubleClicked(QListWidgetItem *item);
|
||||
|
||||
void on_List_itemSelectionChanged();
|
||||
|
||||
private:
|
||||
Ui::Playlist *ui;
|
||||
|
||||
int m_nCurrentPlayListIndex = 0;
|
||||
};
|
||||
|
||||
#endif // PLAYLIST_H
|
||||
71
playlist.ui
Normal file
@@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Playlist</class>
|
||||
<widget class="QWidget" name="Playlist">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>115</width>
|
||||
<height>254</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="MediaList" name="List">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>testlist0</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>testlist1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>testlist2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MediaList</class>
|
||||
<extends>QListWidget</extends>
|
||||
<header location="global">medialist.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
254
playlistwind.cpp
Normal file
@@ -0,0 +1,254 @@
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
#include "playlistwind.h"
|
||||
#include "ui_playlistwind.h"
|
||||
#include "globalhelper.h"
|
||||
PlayListWind::PlayListWind(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::PlayListWind)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
// this->setStyleSheet("QWidget { border: none; }");
|
||||
Init();
|
||||
}
|
||||
|
||||
|
||||
PlayListWind::~PlayListWind()
|
||||
{
|
||||
QStringList strListPlayList;
|
||||
for (int i = 0; i < ui->list->count(); i++)
|
||||
{
|
||||
strListPlayList.append(ui->list->item(i)->toolTip());
|
||||
}
|
||||
GlobalHelper::SavePlaylist(strListPlayList);
|
||||
|
||||
delete ui;
|
||||
}
|
||||
|
||||
bool PlayListWind::Init()
|
||||
{
|
||||
if (ui->list->Init() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (InitUi() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ConnectSignalSlots() == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setAcceptDrops(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayListWind::InitUi()
|
||||
{
|
||||
setStyleSheet(GlobalHelper::GetQssStr(":/qss/res/qss/playlist.css"));
|
||||
ui->list->clear();
|
||||
|
||||
QStringList strListPlaylist;
|
||||
GlobalHelper::GetPlaylist(strListPlaylist);
|
||||
|
||||
for (QString strVideoFile : strListPlaylist)
|
||||
{
|
||||
QFileInfo fileInfo(strVideoFile);
|
||||
if (fileInfo.exists())
|
||||
{
|
||||
QListWidgetItem *pItem = new QListWidgetItem(ui->list);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(QString("%1").arg(fileInfo.fileName())); // 显示文本
|
||||
pItem->setToolTip(fileInfo.filePath());
|
||||
ui->list->addItem(pItem);
|
||||
}
|
||||
}
|
||||
if (strListPlaylist.length() > 0)
|
||||
{
|
||||
ui->list->setCurrentRow(0);
|
||||
}
|
||||
|
||||
//ui->list->addItems(strListPlaylist);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayListWind::ConnectSignalSlots()
|
||||
{
|
||||
QList<bool> listRet;
|
||||
bool bRet;
|
||||
|
||||
bRet = connect(ui->list, &MediaList::SigAddFile, this, &PlayListWind::OnAddFile);
|
||||
listRet.append(bRet);
|
||||
|
||||
for (bool bReturn : listRet)
|
||||
{
|
||||
if (bReturn == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlayListWind::on_List_itemDoubleClicked(QListWidgetItem *item)
|
||||
{
|
||||
emit SigPlay(item->data(Qt::UserRole).toString());
|
||||
m_nCurrentPlayListIndex = ui->list->row(item);
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
|
||||
bool PlayListWind::GetPlaylistStatus()
|
||||
{
|
||||
if (this->isHidden())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int PlayListWind::GetCurrentIndex()
|
||||
{
|
||||
return m_nCurrentPlayListIndex;
|
||||
}
|
||||
|
||||
void PlayListWind::OnRequestPlayCurrentFile()
|
||||
{
|
||||
if(ui->list->count() > 0) // 有文件才会触发请求播放
|
||||
{
|
||||
on_List_itemDoubleClicked(ui->list->item(m_nCurrentPlayListIndex));
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayListWind::OnAddFile(QString strFileName)
|
||||
{
|
||||
bool bSupportMovie = strFileName.endsWith(".mkv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".rmvb", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".mp4", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".avi", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".flv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".wmv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".3gp", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("rtmp://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("https://", Qt::CaseInsensitive) ||
|
||||
strFileName.startsWith("http://", Qt::CaseInsensitive);
|
||||
if (!bSupportMovie)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QFileInfo fileInfo(strFileName);
|
||||
QList<QListWidgetItem *> listItem = ui->list->findItems(fileInfo.fileName(), Qt::MatchExactly);
|
||||
QListWidgetItem *pItem = nullptr;
|
||||
if (listItem.isEmpty())
|
||||
{
|
||||
pItem = new QListWidgetItem(ui->list);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(fileInfo.fileName()); // 显示文件名
|
||||
pItem->setToolTip(fileInfo.filePath()); // 完整文件路径
|
||||
ui->list->addItem(pItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
pItem = listItem.at(0);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayListWind::OnAddFileAndPlay(QString strFileName)
|
||||
{
|
||||
bool bSupportMovie = strFileName.endsWith(".mkv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".rmvb", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".mp4", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".avi", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".flv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".wmv", Qt::CaseInsensitive) ||
|
||||
strFileName.endsWith(".3gp", Qt::CaseInsensitive);
|
||||
if (!bSupportMovie)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QFileInfo fileInfo(strFileName);
|
||||
QList<QListWidgetItem *> listItem = ui->list->findItems(fileInfo.fileName(), Qt::MatchExactly);
|
||||
QListWidgetItem *pItem = nullptr;
|
||||
if (listItem.isEmpty())
|
||||
{
|
||||
pItem = new QListWidgetItem(ui->list);
|
||||
pItem->setData(Qt::UserRole, QVariant(fileInfo.filePath())); // 用户数据
|
||||
pItem->setText(fileInfo.fileName()); // 显示文本
|
||||
pItem->setToolTip(fileInfo.filePath());
|
||||
ui->list->addItem(pItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
pItem = listItem.at(0);
|
||||
}
|
||||
on_List_itemDoubleClicked(pItem);
|
||||
}
|
||||
|
||||
void PlayListWind::OnBackwardPlay()
|
||||
{
|
||||
if (m_nCurrentPlayListIndex == 0)
|
||||
{
|
||||
m_nCurrentPlayListIndex = ui->list->count() - 1;
|
||||
on_List_itemDoubleClicked(ui->list->item(m_nCurrentPlayListIndex));
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nCurrentPlayListIndex--;
|
||||
on_List_itemDoubleClicked(ui->list->item(m_nCurrentPlayListIndex));
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayListWind::OnForwardPlay()
|
||||
{
|
||||
if (m_nCurrentPlayListIndex == ui->list->count() - 1)
|
||||
{
|
||||
m_nCurrentPlayListIndex = 0;
|
||||
on_List_itemDoubleClicked(ui->list->item(m_nCurrentPlayListIndex));
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nCurrentPlayListIndex++;
|
||||
on_List_itemDoubleClicked(ui->list->item(m_nCurrentPlayListIndex));
|
||||
ui->list->setCurrentRow(m_nCurrentPlayListIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayListWind::dropEvent(QDropEvent *event)
|
||||
{
|
||||
QList<QUrl> urls = event->mimeData()->urls();
|
||||
if (urls.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (QUrl url : urls)
|
||||
{
|
||||
QString strFileName = url.toLocalFile();
|
||||
|
||||
OnAddFile(strFileName);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayListWind::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void PlayListWind::on_List_itemSelectionChanged()
|
||||
{
|
||||
m_nCurrentPlayListIndex = ui->list->currentIndex().row(); // 获取选中后的新位置。
|
||||
}
|
||||
83
playlistwind.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#ifndef PLAYLISTWIND_H
|
||||
#define PLAYLISTWIND_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QListWidgetItem>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QMimeData>
|
||||
namespace Ui {
|
||||
class PlayListWind;
|
||||
}
|
||||
|
||||
class PlayListWind : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlayListWind(QWidget *parent = 0);
|
||||
~PlayListWind();
|
||||
|
||||
bool Init();
|
||||
|
||||
|
||||
/**
|
||||
* @brief 获取播放列表状态
|
||||
*
|
||||
* @return true 显示 false 隐藏
|
||||
* @note
|
||||
*/
|
||||
bool GetPlaylistStatus();
|
||||
int GetCurrentIndex();
|
||||
public:
|
||||
/**
|
||||
* @brief 添加文件
|
||||
*
|
||||
* @param strFileName 文件完整路径
|
||||
* @note
|
||||
*/
|
||||
void OnAddFile(QString strFileName);
|
||||
void OnAddFileAndPlay(QString strFileName);
|
||||
|
||||
void OnBackwardPlay();
|
||||
void OnForwardPlay();
|
||||
void OnRequestPlayCurrentFile();
|
||||
/* 在这里定义dock的初始大小 */
|
||||
QSize sizeHint() const
|
||||
{
|
||||
return QSize(150, 900);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 放下事件
|
||||
*
|
||||
* @param event 事件指针
|
||||
* @note
|
||||
*/
|
||||
void dropEvent(QDropEvent *event);
|
||||
/**
|
||||
* @brief 拖动事件
|
||||
*
|
||||
* @param event 事件指针
|
||||
* @note
|
||||
*/
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
|
||||
signals:
|
||||
void SigUpdateUi(); //< 界面排布更新
|
||||
void SigPlay(QString strFile); //< 播放文件
|
||||
|
||||
private:
|
||||
bool InitUi();
|
||||
bool ConnectSignalSlots();
|
||||
|
||||
private slots:
|
||||
|
||||
void on_List_itemDoubleClicked(QListWidgetItem *item);
|
||||
|
||||
void on_List_itemSelectionChanged();
|
||||
private:
|
||||
Ui::PlayListWind *ui;
|
||||
int m_nCurrentPlayListIndex = 0;
|
||||
};
|
||||
|
||||
#endif // PLAYLISTWIND_H
|
||||
66
playlistwind.ui
Normal file
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PlayListWind</class>
|
||||
<widget class="QWidget" name="PlayListWind">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>150</width>
|
||||
<height>254</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="MediaList" name="list">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>周杰伦-双截棍</string>
|
||||
</property>
|
||||
<property name="background">
|
||||
<brush brushstyle="NoBrush">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>0</green>
|
||||
<blue>0</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>林俊杰</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>MediaList</class>
|
||||
<extends>QListWidget</extends>
|
||||
<header location="global">medialist.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
BIN
res/array_down.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
res/checkbox_checked.png
Normal file
|
After Width: | Height: | Size: 320 B |
BIN
res/checkbox_checked_disable.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
res/checkbox_parcial.png
Normal file
|
After Width: | Height: | Size: 203 B |
BIN
res/checkbox_parcial_disable.png
Normal file
|
After Width: | Height: | Size: 256 B |
BIN
res/checkbox_unchecked.png
Normal file
|
After Width: | Height: | Size: 151 B |
BIN
res/checkbox_unchecked_disable.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
res/fa-solid-900.ttf
Normal file
BIN
res/fontawesome-webfont.ttf
Normal file
BIN
res/icon/fast-backward.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
res/icon/fast-forward.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
res/icon/next.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/icon/pause.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
res/icon/play.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
res/icon/previous.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/icon/quick.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
res/icon/resize.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
res/icon/rules.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
res/icon/screenshot.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
res/icon/stop-button.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
res/icon/tune.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
48
res/qss/about.css
Normal file
@@ -0,0 +1,48 @@
|
||||
*{
|
||||
background-color:#202129;
|
||||
color:white;
|
||||
}
|
||||
|
||||
/**********页签项**********/
|
||||
QTabWidget::pane {
|
||||
border: none;
|
||||
border-top: 3px solid rgb(0, 160, 230);
|
||||
background: rgb(57, 58, 60);
|
||||
}
|
||||
QTabWidget::tab-bar {
|
||||
border: none;
|
||||
}
|
||||
|
||||
QTabBar::tab {
|
||||
border: none;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
color: rgb(175, 175, 175);
|
||||
background: rgb(255, 255, 255, 30);
|
||||
height: 28px;
|
||||
min-width: 85px;
|
||||
margin-right: 5px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
QTabBar::tab:hover {
|
||||
background: rgb(255, 255, 255, 40);
|
||||
}
|
||||
QTabBar::tab:selected {
|
||||
color: white;
|
||||
background: rgb(0, 160, 230);
|
||||
}
|
||||
|
||||
QPushButton{
|
||||
height: 23px;
|
||||
width: 75px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: rgb(0, 160, 230);
|
||||
}
|
||||
QPushButton:hover{
|
||||
background: rgba(0, 161, 230, 0.582);
|
||||
}
|
||||
QPushButton:press{
|
||||
background: rgb(0, 179, 255);
|
||||
}
|
||||
91
res/qss/ctrlbar.css
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
QWidget#BgWidget{
|
||||
background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,
|
||||
stop: 0 #374040, stop: 1.0 #222930);
|
||||
color:white;
|
||||
}*/
|
||||
|
||||
*{
|
||||
background-color:transparent;
|
||||
color:white;
|
||||
}
|
||||
QWidget#PlaySliderBgWidget{
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
/*青色*/
|
||||
QPushButton:hover{
|
||||
color: Cyan;
|
||||
}
|
||||
/*军校蓝*/
|
||||
QPushButton:pressed{
|
||||
color: CadetBlue;
|
||||
}
|
||||
|
||||
|
||||
/*滑竿*/
|
||||
QSlider::groove:horizontal {
|
||||
border: 1px solid #4A708B;
|
||||
background: #C0C0C0;
|
||||
height: 3px;
|
||||
border-radius: 2px;
|
||||
padding-left:-1px;
|
||||
padding-right:-1px;
|
||||
}
|
||||
/*已经划过的从地方*/
|
||||
QSlider::sub-page:horizontal {
|
||||
background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
|
||||
stop:0 #B1B1B1, stop:1 #c4c4c4);
|
||||
background: qlineargradient(x1: 0, y1: 0.2, x2: 1, y2: 1,
|
||||
stop: 0 #5DCCFF, stop: 1 #1874CD);
|
||||
border: 1px solid #4A708B;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/*还没有滑上去的地方*/
|
||||
QSlider::add-page:horizontal {
|
||||
background: #575757;
|
||||
border: 0px solid #777;
|
||||
border-radius: 2px;
|
||||
}
|
||||
/*中间滑块*/
|
||||
QSlider::handle:horizontal
|
||||
{
|
||||
/*背景颜色设置为辐射聚焦*/
|
||||
background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5,
|
||||
stop:0.6 #45ADED, stop:0.8 rgba(255, 255, 255, 255));
|
||||
|
||||
/*形成圆*/
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
/*上沿、下沿超出滑竿*/
|
||||
margin-top: -3px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal:hover {
|
||||
background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 #2A8BDA,
|
||||
stop:0.8 rgba(255, 255, 255, 255));
|
||||
|
||||
/*形成圆*/
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
|
||||
/*上沿、下沿超出滑竿*/
|
||||
margin-top: -3px;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
QTimeEdit#VideoTotalTimeTimeEdit {
|
||||
color: #c4c6d2;
|
||||
}
|
||||
|
||||
QLabel#TimeSplitLabel {
|
||||
color: #c4c6d2;
|
||||
}
|
||||
|
||||
/**********提示**********/
|
||||
QToolTip{
|
||||
border: none;
|
||||
background-color: #2e2f37;
|
||||
color:white;
|
||||
}
|
||||
641
res/qss/homewindow.css
Normal file
@@ -0,0 +1,641 @@
|
||||
/*#COLOR#;MAIN_COLOR=#45b0c4;BTN_HOVER_COLOR=#6bc3ce;BTN_PRESSED_COLOR=#30889b;ITEM_HOVER_COLOR=#f1fcfc;ITEM_SELECTED_COLOR=#e9f4f4*/
|
||||
/**********窗口样式*************/
|
||||
|
||||
QMainWindow {
|
||||
background: #FFFFFF;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.QToolButton {
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
}
|
||||
.QToolButton::hover {
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
|
||||
}
|
||||
/*QGroupBox#typeGroupBox{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
QWidget#settingWidget{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
QWidget#optWidget{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
|
||||
QWidget#QSkinDemoClass,
|
||||
QWidget#QSkinEditDialog,
|
||||
QWidget#QMyMessageBox,
|
||||
QWidget#QAboutDialog
|
||||
{
|
||||
border:1px solid #45b0c4;
|
||||
border-radius:0px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.QFrame{
|
||||
border:1px solid #45b0c4;
|
||||
border-radius:5px;
|
||||
}*/
|
||||
|
||||
/***************标题栏*****************/
|
||||
/*QWidget#widget_title{
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
/*QMainWindow#windowTitle{
|
||||
border:1px solid red;
|
||||
background: #45b0c4;
|
||||
font-size: 20px;
|
||||
}*/
|
||||
/*QWidget#widget_title2{
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
|
||||
|
||||
/**********菜单栏窗口样式*************/
|
||||
/*QWidget#widget_menu{*/
|
||||
/*background: #f1fcfc;*/
|
||||
/* border: 1px solid #45b0c4;*/
|
||||
/*border-left: none;*/
|
||||
/*border-right: none;*/
|
||||
/*}*/
|
||||
|
||||
/*QWidget#previewWidget{
|
||||
background: #45b0c4;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
/**********状态栏窗口样式*************/
|
||||
/*QWidget#widget_status{
|
||||
background: #45b0c4;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}*/
|
||||
|
||||
/*QLabel#label_logo{
|
||||
image: url(:/Resources/logo.png);
|
||||
}
|
||||
|
||||
QLabel#label_title{
|
||||
border-radius:0px;
|
||||
color: #FFFFFF;
|
||||
background-color:rgba(0,0,0,0);
|
||||
border-style:none;
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="information"] {
|
||||
qproperty-pixmap: url(:/res/information);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="error"] {
|
||||
qproperty-pixmap: url(:/res/error);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="success"] {
|
||||
qproperty-pixmap: url(:/res/success);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="question"] {
|
||||
qproperty-pixmap: url(:/res/question);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="warning"] {
|
||||
qproperty-pixmap: url(:/res/warning);
|
||||
}*/
|
||||
|
||||
/**********文本编辑框**********/
|
||||
/*QTextEdit,QLineEdit {
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 1px;
|
||||
padding: 2px;
|
||||
background: none;
|
||||
selection-background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QLineEdit[echoMode="2"] {
|
||||
lineedit-password-character: 9679;
|
||||
}
|
||||
|
||||
.QGroupBox{
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 1px;
|
||||
margin-top: 1ex;
|
||||
}
|
||||
.QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
|
||||
.QPushButton{
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 5px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
|
||||
/*.QPushButton[focusPolicy="0"] {
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 0px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QPushButton:hover{
|
||||
background: #6bc3ce
|
||||
}
|
||||
|
||||
.QPushButton:pressed{
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
|
||||
.QToolButton{
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 4px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QToolButton[focusPolicy="0"] {
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 0px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QToolButton:hover{
|
||||
background: #6bc3ce
|
||||
}*/
|
||||
|
||||
/*.QToolButton:pressed{
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"] {
|
||||
border: none;
|
||||
padding-top:5px;
|
||||
background: none;
|
||||
color: #000000;
|
||||
border-radius:0px;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"]:hover {
|
||||
background-color:#EEEEEE;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"]:pressed {
|
||||
border: 4px solid #45b0c4;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
background-color:#EEEEEE;
|
||||
}
|
||||
.QToolButton[pageTab="true"]:checked {
|
||||
border: 4px solid #45b0c4;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
background-color:#EEEEEE;
|
||||
}
|
||||
|
||||
.QToolButton[TopPageTab="true"] {
|
||||
border: none;
|
||||
padding-top:10px;
|
||||
color: #FFFFFF;
|
||||
background: none;
|
||||
border-radius:0px;
|
||||
}*/
|
||||
|
||||
/*.QToolButton[TopPageTab="true"]:hover {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.QToolButton[TopPageTab="true"]:pressed {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.QToolButton[TopPageTab="true"]:checked {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QPushButton#menuButton_Min,QPushButton#menuButton_Max,QPushButton#menuButton_Close{
|
||||
border-radius:0px;
|
||||
color: #FFFFFF;
|
||||
background-color:rgba(0,0,0,0);
|
||||
border-style:none;
|
||||
}
|
||||
|
||||
QPushButton#menuButton_Min:hover,QPushButton#menuButton_Max:hover{
|
||||
background-color: #6bc3ce;
|
||||
}
|
||||
QPushButton#menuButton_Min:pressed,QPushButton#menuButton_Max:pressed{
|
||||
background-color: #30889b;
|
||||
}
|
||||
QPushButton#menuButton_Close:hover{
|
||||
background-color: #FF5439;
|
||||
}
|
||||
QPushButton#menuButton_Close:pressed{
|
||||
background-color: #E04A32;
|
||||
}
|
||||
|
||||
QCheckBox {
|
||||
spacing: 2px;
|
||||
}*/
|
||||
|
||||
/*QCheckBox::indicator, QTableView::indicator, QListView::indicator, QTreeView::indicator, QGroupBox::indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:unchecked, QTableView::indicator:unchecked, QListView::indicator:unchecked, QTreeView::indicator:unchecked, QGroupBox::indicator:unchecked {
|
||||
image: url(:/res/checkbox_unchecked.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked, QTableView::indicator:checked, QListView::indicator:checked, QTreeView::indicator:checked, QGroupBox::indicator:checked {
|
||||
image: url(:/res/checkbox_checked.png);
|
||||
}
|
||||
|
||||
QRadioButton {
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator::unchecked {
|
||||
image: url(:/res/radio_normal.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator::checked {
|
||||
image: url(:/res/radio_selected.png);
|
||||
}*/
|
||||
|
||||
/*QComboBox,QDateEdit,QDateTimeEdit,QTimeEdit,QDoubleSpinBox,QSpinBox{
|
||||
border-radius: 1px;
|
||||
padding: 1px 5px 1px 5px;
|
||||
border: 1px solid #45b0c4;
|
||||
selection-background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QComboBox::drop-down,QDateEdit::drop-down,QDateTimeEdit::drop-down,QTimeEdit::drop-down,QDoubleSpinBox::drop-down,QSpinBox::drop-down {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: top right;
|
||||
width: 15px;
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-left-color: #45b0c4;
|
||||
}
|
||||
|
||||
QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow {
|
||||
image: url(:/res/array_down.png);
|
||||
width:10px;
|
||||
height:9px;
|
||||
}
|
||||
|
||||
QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{
|
||||
image: url(:/res/array_up.png);
|
||||
width:10px;
|
||||
height:10px;
|
||||
padding:0px 3px 0px 0px;
|
||||
}
|
||||
|
||||
QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{
|
||||
image: url(:/res/array_down.png);
|
||||
width:10px;
|
||||
height:10px;
|
||||
padding:0px 3px 0px 0px;
|
||||
}
|
||||
|
||||
/**********菜单栏**********/
|
||||
/*QMenuBar {
|
||||
background: #f1fcfc;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
QMenuBar::item {
|
||||
border: 1px solid transparent;
|
||||
padding: 5px 10px 5px 10px;
|
||||
background: transparent;
|
||||
}
|
||||
QMenuBar::item:enabled {
|
||||
color: #000000;
|
||||
}
|
||||
QMenuBar::item:!enabled {
|
||||
color: rgb(155, 155, 155);
|
||||
}
|
||||
QMenuBar::item:enabled:selected {
|
||||
background: #e9f4f4;
|
||||
}*/
|
||||
|
||||
/*QMenu {
|
||||
background-color:#FFFFFF;*/
|
||||
/*margin: 2px;*/
|
||||
/* border: 1px solid #45b0c4;
|
||||
}*/
|
||||
|
||||
/*QMenu::item {
|
||||
padding: 2px 20px 2px 20px;
|
||||
}
|
||||
|
||||
QMenu::indicator {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
color: #FFFFFF;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QProgressBar {
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
border: 1px solid #45b0c4;
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
width: 5px;
|
||||
margin: 0.5px;
|
||||
background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::groove:horizontal,QSlider::add-page:horizontal {
|
||||
background: #D9D9D9;
|
||||
height: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QSlider::sub-page:horizontal {
|
||||
height: 8px;
|
||||
border-radius: 3px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal {
|
||||
width: 13px;
|
||||
margin-top: -3px;
|
||||
margin-bottom: -3px;
|
||||
border-radius: 6px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal:hover {
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QSlider::groove:vertical,QSlider::sub-page:vertical {
|
||||
background:#D9D9D9;
|
||||
width: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QSlider::add-page:vertical {
|
||||
width: 8px;
|
||||
border-radius: 3px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:vertical {
|
||||
height: 14px;
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
border-radius: 6px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:vertical:hover {
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar:vertical {
|
||||
width:10px;
|
||||
background-color:rgba(0,0,0,0);
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
}
|
||||
|
||||
QScrollBar:horizontal {
|
||||
height:10px;
|
||||
background-color:rgba(0,0,0,0);
|
||||
padding-left:10px;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
width:10px;
|
||||
background: #45b0c4;
|
||||
border-radius:4px;
|
||||
min-height:50px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal {
|
||||
height:10px;
|
||||
background: #45b0c4;
|
||||
min-width:50px;
|
||||
border-radius:4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:hover {
|
||||
width:10px;
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
height:10px;
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar::add-line:vertical {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: bottom;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/add-line_vertical.png);
|
||||
}
|
||||
|
||||
QScrollBar::add-line:horizontal {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: right;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/add-line_horizontal.png);
|
||||
}
|
||||
|
||||
QScrollBar::sub-line:vertical {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: top;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/sub-line_vertical.png);
|
||||
}
|
||||
|
||||
QScrollBar::sub-line:horizontal {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: left;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/sub-line_horizontal.png);
|
||||
}
|
||||
|
||||
QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical {
|
||||
width:10px;
|
||||
background: #D9D9D9;
|
||||
}
|
||||
|
||||
QScrollBar::add-page:horizontal,QScrollBar::sub-page:horizontal {
|
||||
height:10px;
|
||||
background: #D9D9D9;
|
||||
}
|
||||
|
||||
QScrollArea {
|
||||
border: 0px ;
|
||||
}*/
|
||||
|
||||
/*QTreeView,QListView,QTableView{
|
||||
border: 1px solid #45b0c4;
|
||||
selection-background-color: #45b0c4;
|
||||
outline:0px;
|
||||
}
|
||||
|
||||
QTableView::item:selected, QListView::item:selected, QTreeView::item:selected {
|
||||
color: #000000;
|
||||
background: #e9f4f4;
|
||||
}
|
||||
|
||||
QTableView::item:hover, QListView::item:hover, QTreeView::item:hover {
|
||||
color: #000000;
|
||||
background: #f1fcfc;
|
||||
}
|
||||
|
||||
QTableView::item, QListView::item, QTreeView::item {
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
padding:3px;
|
||||
margin:0px;
|
||||
color:#FFFFFF;
|
||||
border: 1px solid #F0F0F0;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QTabBar::tab{
|
||||
min-width: 80px;
|
||||
min-height: 25px;
|
||||
|
||||
color:#000000;
|
||||
margin-right:1px;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
background:#FFFFFF;
|
||||
}
|
||||
|
||||
QTabBar::tab:selected,QTabBar::tab:hover{
|
||||
border-style:solid;
|
||||
border-color:#45b0c4;
|
||||
}
|
||||
|
||||
QTabBar::tab:top,QTabBar::tab:bottom{
|
||||
padding:3px 8px 3px 8px;
|
||||
}
|
||||
|
||||
QTabBar::tab:left,QTabBar::tab:right{
|
||||
padding:8px 3px 8px 3px;
|
||||
}
|
||||
|
||||
QTabBar::tab:top:selected,QTabBar::tab:top:hover{
|
||||
border-width:2px 0px 0px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:right:selected,QTabBar::tab:right:hover{
|
||||
border-width:0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
|
||||
border-width:0px 0px 2px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:left:selected,QTabBar::tab:left:hover{
|
||||
border-width:0px 2px 0px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:top:selected,QTabBar::tab:top:hover,QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:first:top:selected,QTabBar::tab:first:top:hover,QTabBar::tab:first:bottom:selected,QTabBar::tab:first:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:first:left:selected,QTabBar::tab:first:left:hover,QTabBar::tab:first:right:selected,QTabBar::tab:first:right:hover{
|
||||
border-top-width:1px;
|
||||
border-top-color:#D9D9D9;
|
||||
border-bottom-width:1px;
|
||||
border-bottom-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:last:top:selected,QTabBar::tab:last:top:hover,QTabBar::tab:last:bottom:selected,QTabBar::tab:last:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:last:left:selected,QTabBar::tab:last:left:hover,QTabBar::tab:last:right:selected,QTabBar::tab:last:right:hover{
|
||||
border-top-width:1px;
|
||||
border-top-color:#D9D9D9;
|
||||
border-bottom-width:1px;
|
||||
border-bottom-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QStatusBar::item {
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 3px;
|
||||
}*/
|
||||
649
res/qss/homewindow.qss
Normal file
@@ -0,0 +1,649 @@
|
||||
/*#COLOR#;MAIN_COLOR=#45b0c4;BTN_HOVER_COLOR=#6bc3ce;BTN_PRESSED_COLOR=#30889b;ITEM_HOVER_COLOR=#f1fcfc;ITEM_SELECTED_COLOR=#e9f4f4*/
|
||||
/**********窗口样式*************/
|
||||
|
||||
QMainWindow {
|
||||
/* background: #FFFFFF; */
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.QToolButton {
|
||||
background-color: transparent;
|
||||
/* border-style: none; */
|
||||
border: 1px solid #FFFFFF;
|
||||
/* icon-size: 48px; */
|
||||
padding: 8px;
|
||||
/* margin: 2px; */
|
||||
/* transition: all 0.3s ease; */
|
||||
}
|
||||
.QToolButton::hover {
|
||||
background-color: transparent;
|
||||
border-style: none;
|
||||
/* icon-size: 60px; */
|
||||
padding: 3px;
|
||||
|
||||
|
||||
}
|
||||
/*QGroupBox#typeGroupBox{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
QWidget#settingWidget{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
QWidget#optWidget{
|
||||
border:1px solid #45b0c4;
|
||||
}
|
||||
|
||||
QWidget#QSkinDemoClass,
|
||||
QWidget#QSkinEditDialog,
|
||||
QWidget#QMyMessageBox,
|
||||
QWidget#QAboutDialog
|
||||
{
|
||||
border:1px solid #45b0c4;
|
||||
border-radius:0px;
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
|
||||
.QFrame{
|
||||
border:1px solid #45b0c4;
|
||||
border-radius:5px;
|
||||
}*/
|
||||
|
||||
/***************标题栏*****************/
|
||||
/*QWidget#widget_title{
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
/*QMainWindow#windowTitle{
|
||||
border:1px solid red;
|
||||
background: #45b0c4;
|
||||
font-size: 20px;
|
||||
}*/
|
||||
/*QWidget#widget_title2{
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
|
||||
|
||||
/**********菜单栏窗口样式*************/
|
||||
/*QWidget#widget_menu{*/
|
||||
/*background: #f1fcfc;*/
|
||||
/* border: 1px solid #45b0c4;*/
|
||||
/*border-left: none;*/
|
||||
/*border-right: none;*/
|
||||
/*}*/
|
||||
|
||||
/*QWidget#previewWidget{
|
||||
background: #45b0c4;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
|
||||
/**********状态栏窗口样式*************/
|
||||
/*QWidget#widget_status{
|
||||
background: #45b0c4;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}*/
|
||||
|
||||
/*QLabel#label_logo{
|
||||
image: url(:/Resources/logo.png);
|
||||
}
|
||||
|
||||
QLabel#label_title{
|
||||
border-radius:0px;
|
||||
color: #FFFFFF;
|
||||
background-color:rgba(0,0,0,0);
|
||||
border-style:none;
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="information"] {
|
||||
qproperty-pixmap: url(:/res/information);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="error"] {
|
||||
qproperty-pixmap: url(:/res/error);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="success"] {
|
||||
qproperty-pixmap: url(:/res/success);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="question"] {
|
||||
qproperty-pixmap: url(:/res/question);
|
||||
}
|
||||
|
||||
QLabel#label_MessageType[MessageType="warning"] {
|
||||
qproperty-pixmap: url(:/res/warning);
|
||||
}*/
|
||||
|
||||
/**********文本编辑框**********/
|
||||
/*QTextEdit,QLineEdit {
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 1px;
|
||||
padding: 2px;
|
||||
background: none;
|
||||
selection-background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QLineEdit[echoMode="2"] {
|
||||
lineedit-password-character: 9679;
|
||||
}
|
||||
|
||||
.QGroupBox{
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 1px;
|
||||
margin-top: 1ex;
|
||||
}
|
||||
.QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
position: relative;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
|
||||
.QPushButton{
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 5px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}*/
|
||||
|
||||
/*.QPushButton[focusPolicy="0"] {
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 0px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QPushButton:hover{
|
||||
background: #6bc3ce
|
||||
}
|
||||
|
||||
.QPushButton:pressed{
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
|
||||
.QToolButton{
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 4px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QToolButton[focusPolicy="0"] {
|
||||
border-style: none;
|
||||
border: 0px;
|
||||
color: #FFFFFF;
|
||||
padding: 0px;
|
||||
border-radius:1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
.QToolButton:hover{
|
||||
background: #6bc3ce
|
||||
}*/
|
||||
|
||||
/*.QToolButton:pressed{
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"] {
|
||||
border: none;
|
||||
padding-top:5px;
|
||||
background: none;
|
||||
color: #000000;
|
||||
border-radius:0px;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"]:hover {
|
||||
background-color:#EEEEEE;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.QToolButton[pageTab="true"]:pressed {
|
||||
border: 4px solid #45b0c4;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
background-color:#EEEEEE;
|
||||
}
|
||||
.QToolButton[pageTab="true"]:checked {
|
||||
border: 4px solid #45b0c4;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
background-color:#EEEEEE;
|
||||
}
|
||||
|
||||
.QToolButton[TopPageTab="true"] {
|
||||
border: none;
|
||||
padding-top:10px;
|
||||
color: #FFFFFF;
|
||||
background: none;
|
||||
border-radius:0px;
|
||||
}*/
|
||||
|
||||
/*.QToolButton[TopPageTab="true"]:hover {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.QToolButton[TopPageTab="true"]:pressed {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.QToolButton[TopPageTab="true"]:checked {
|
||||
background-color:qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(0, 0, 0, 0%), stop:1 rgba(4, 20, 7, 10%));
|
||||
border: none;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
QPushButton#menuButton_Min,QPushButton#menuButton_Max,QPushButton#menuButton_Close{
|
||||
border-radius:0px;
|
||||
color: #FFFFFF;
|
||||
background-color:rgba(0,0,0,0);
|
||||
border-style:none;
|
||||
}
|
||||
|
||||
QPushButton#menuButton_Min:hover,QPushButton#menuButton_Max:hover{
|
||||
background-color: #6bc3ce;
|
||||
}
|
||||
QPushButton#menuButton_Min:pressed,QPushButton#menuButton_Max:pressed{
|
||||
background-color: #30889b;
|
||||
}
|
||||
QPushButton#menuButton_Close:hover{
|
||||
background-color: #FF5439;
|
||||
}
|
||||
QPushButton#menuButton_Close:pressed{
|
||||
background-color: #E04A32;
|
||||
}
|
||||
|
||||
QCheckBox {
|
||||
spacing: 2px;
|
||||
}*/
|
||||
|
||||
/*QCheckBox::indicator, QTableView::indicator, QListView::indicator, QTreeView::indicator, QGroupBox::indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
QCheckBox::indicator:unchecked, QTableView::indicator:unchecked, QListView::indicator:unchecked, QTreeView::indicator:unchecked, QGroupBox::indicator:unchecked {
|
||||
image: url(:/res/checkbox_unchecked.png);
|
||||
}
|
||||
|
||||
QCheckBox::indicator:checked, QTableView::indicator:checked, QListView::indicator:checked, QTreeView::indicator:checked, QGroupBox::indicator:checked {
|
||||
image: url(:/res/checkbox_checked.png);
|
||||
}
|
||||
|
||||
QRadioButton {
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
QRadioButton::indicator::unchecked {
|
||||
image: url(:/res/radio_normal.png);
|
||||
}
|
||||
|
||||
QRadioButton::indicator::checked {
|
||||
image: url(:/res/radio_selected.png);
|
||||
}*/
|
||||
|
||||
/*QComboBox,QDateEdit,QDateTimeEdit,QTimeEdit,QDoubleSpinBox,QSpinBox{
|
||||
border-radius: 1px;
|
||||
padding: 1px 5px 1px 5px;
|
||||
border: 1px solid #45b0c4;
|
||||
selection-background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QComboBox::drop-down,QDateEdit::drop-down,QDateTimeEdit::drop-down,QTimeEdit::drop-down,QDoubleSpinBox::drop-down,QSpinBox::drop-down {
|
||||
subcontrol-origin: padding;
|
||||
subcontrol-position: top right;
|
||||
width: 15px;
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-left-color: #45b0c4;
|
||||
}
|
||||
|
||||
QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow {
|
||||
image: url(:/res/array_down.png);
|
||||
width:10px;
|
||||
height:9px;
|
||||
}
|
||||
|
||||
QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{
|
||||
image: url(:/res/array_up.png);
|
||||
width:10px;
|
||||
height:10px;
|
||||
padding:0px 3px 0px 0px;
|
||||
}
|
||||
|
||||
QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{
|
||||
image: url(:/res/array_down.png);
|
||||
width:10px;
|
||||
height:10px;
|
||||
padding:0px 3px 0px 0px;
|
||||
}
|
||||
|
||||
/**********菜单栏**********/
|
||||
/*QMenuBar {
|
||||
background: #f1fcfc;
|
||||
border: 1px solid #45b0c4;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
QMenuBar::item {
|
||||
border: 1px solid transparent;
|
||||
padding: 5px 10px 5px 10px;
|
||||
background: transparent;
|
||||
}
|
||||
QMenuBar::item:enabled {
|
||||
color: #000000;
|
||||
}
|
||||
QMenuBar::item:!enabled {
|
||||
color: rgb(155, 155, 155);
|
||||
}
|
||||
QMenuBar::item:enabled:selected {
|
||||
background: #e9f4f4;
|
||||
}*/
|
||||
|
||||
/*QMenu {
|
||||
background-color:#FFFFFF;*/
|
||||
/*margin: 2px;*/
|
||||
/* border: 1px solid #45b0c4;
|
||||
}*/
|
||||
|
||||
/*QMenu::item {
|
||||
padding: 2px 20px 2px 20px;
|
||||
}
|
||||
|
||||
QMenu::indicator {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
color: #FFFFFF;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QProgressBar {
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
border: 1px solid #45b0c4;
|
||||
}
|
||||
|
||||
QProgressBar::chunk {
|
||||
width: 5px;
|
||||
margin: 0.5px;
|
||||
background-color: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::groove:horizontal,QSlider::add-page:horizontal {
|
||||
background: #D9D9D9;
|
||||
height: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QSlider::sub-page:horizontal {
|
||||
height: 8px;
|
||||
border-radius: 3px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal {
|
||||
width: 13px;
|
||||
margin-top: -3px;
|
||||
margin-bottom: -3px;
|
||||
border-radius: 6px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal:hover {
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QSlider::groove:vertical,QSlider::sub-page:vertical {
|
||||
background:#D9D9D9;
|
||||
width: 8px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
QSlider::add-page:vertical {
|
||||
width: 8px;
|
||||
border-radius: 3px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:vertical {
|
||||
height: 14px;
|
||||
margin-left: -3px;
|
||||
margin-right: -3px;
|
||||
border-radius: 6px;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QSlider::handle:vertical:hover {
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar:vertical {
|
||||
width:10px;
|
||||
background-color:rgba(0,0,0,0);
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
}
|
||||
|
||||
QScrollBar:horizontal {
|
||||
height:10px;
|
||||
background-color:rgba(0,0,0,0);
|
||||
padding-left:10px;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical {
|
||||
width:10px;
|
||||
background: #45b0c4;
|
||||
border-radius:4px;
|
||||
min-height:50px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal {
|
||||
height:10px;
|
||||
background: #45b0c4;
|
||||
min-width:50px;
|
||||
border-radius:4px;
|
||||
}
|
||||
|
||||
QScrollBar::handle:vertical:hover {
|
||||
width:10px;
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar::handle:horizontal:hover {
|
||||
height:10px;
|
||||
background: #30889b;
|
||||
}
|
||||
|
||||
QScrollBar::add-line:vertical {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: bottom;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/add-line_vertical.png);
|
||||
}
|
||||
|
||||
QScrollBar::add-line:horizontal {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: right;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/add-line_horizontal.png);
|
||||
}
|
||||
|
||||
QScrollBar::sub-line:vertical {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: top;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/sub-line_vertical.png);
|
||||
}
|
||||
|
||||
QScrollBar::sub-line:horizontal {
|
||||
height:10px;
|
||||
width:10px;
|
||||
subcontrol-position: left;
|
||||
subcontrol-origin: margin;
|
||||
border-image:url(:/res/sub-line_horizontal.png);
|
||||
}
|
||||
|
||||
QScrollBar::add-page:vertical,QScrollBar::sub-page:vertical {
|
||||
width:10px;
|
||||
background: #D9D9D9;
|
||||
}
|
||||
|
||||
QScrollBar::add-page:horizontal,QScrollBar::sub-page:horizontal {
|
||||
height:10px;
|
||||
background: #D9D9D9;
|
||||
}
|
||||
|
||||
QScrollArea {
|
||||
border: 0px ;
|
||||
}*/
|
||||
|
||||
/*QTreeView,QListView,QTableView{
|
||||
border: 1px solid #45b0c4;
|
||||
selection-background-color: #45b0c4;
|
||||
outline:0px;
|
||||
}
|
||||
|
||||
QTableView::item:selected, QListView::item:selected, QTreeView::item:selected {
|
||||
color: #000000;
|
||||
background: #e9f4f4;
|
||||
}
|
||||
|
||||
QTableView::item:hover, QListView::item:hover, QTreeView::item:hover {
|
||||
color: #000000;
|
||||
background: #f1fcfc;
|
||||
}
|
||||
|
||||
QTableView::item, QListView::item, QTreeView::item {
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
padding:3px;
|
||||
margin:0px;
|
||||
color:#FFFFFF;
|
||||
border: 1px solid #F0F0F0;
|
||||
background: #45b0c4;
|
||||
}
|
||||
|
||||
QTabBar::tab{
|
||||
min-width: 80px;
|
||||
min-height: 25px;
|
||||
|
||||
color:#000000;
|
||||
margin-right:1px;
|
||||
border: 1px solid #D9D9D9;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
background:#FFFFFF;
|
||||
}
|
||||
|
||||
QTabBar::tab:selected,QTabBar::tab:hover{
|
||||
border-style:solid;
|
||||
border-color:#45b0c4;
|
||||
}
|
||||
|
||||
QTabBar::tab:top,QTabBar::tab:bottom{
|
||||
padding:3px 8px 3px 8px;
|
||||
}
|
||||
|
||||
QTabBar::tab:left,QTabBar::tab:right{
|
||||
padding:8px 3px 8px 3px;
|
||||
}
|
||||
|
||||
QTabBar::tab:top:selected,QTabBar::tab:top:hover{
|
||||
border-width:2px 0px 0px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:right:selected,QTabBar::tab:right:hover{
|
||||
border-width:0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
|
||||
border-width:0px 0px 2px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:left:selected,QTabBar::tab:left:hover{
|
||||
border-width:0px 2px 0px 0px;
|
||||
}
|
||||
|
||||
QTabBar::tab:top:selected,QTabBar::tab:top:hover,QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:first:top:selected,QTabBar::tab:first:top:hover,QTabBar::tab:first:bottom:selected,QTabBar::tab:first:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:first:left:selected,QTabBar::tab:first:left:hover,QTabBar::tab:first:right:selected,QTabBar::tab:first:right:hover{
|
||||
border-top-width:1px;
|
||||
border-top-color:#D9D9D9;
|
||||
border-bottom-width:1px;
|
||||
border-bottom-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:last:top:selected,QTabBar::tab:last:top:hover,QTabBar::tab:last:bottom:selected,QTabBar::tab:last:bottom:hover{
|
||||
border-left-width:1px;
|
||||
border-left-color:#D9D9D9;
|
||||
border-right-width:1px;
|
||||
border-right-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QTabBar::tab:last:left:selected,QTabBar::tab:last:left:hover,QTabBar::tab:last:right:selected,QTabBar::tab:last:right:hover{
|
||||
border-top-width:1px;
|
||||
border-top-color:#D9D9D9;
|
||||
border-bottom-width:1px;
|
||||
border-bottom-color:#D9D9D9;
|
||||
}
|
||||
|
||||
QStatusBar::item {
|
||||
border: 1px solid #45b0c4;
|
||||
border-radius: 3px;
|
||||
}*/
|
||||
71
res/qss/playlist.css
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
*{
|
||||
background:#2e2f37;
|
||||
color:white;
|
||||
}
|
||||
|
||||
QPushButton{
|
||||
color: white;
|
||||
}
|
||||
QPushButton:hover{
|
||||
color: Cyan;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
color: CadetBlue;
|
||||
}
|
||||
|
||||
/*****列表*******/
|
||||
QListWidget{
|
||||
border: 1px solid Black;
|
||||
}
|
||||
QListWidget::item:hover{
|
||||
/*background: Cyan;*/
|
||||
padding: 0px;
|
||||
margin: 1px;
|
||||
color: Cyan;
|
||||
border: 1px solid Cyan;
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background: Cyan;
|
||||
padding: 0px;
|
||||
margin: 1px;
|
||||
color: black;
|
||||
border: 1px solid Cyan;
|
||||
}
|
||||
|
||||
|
||||
|
||||
QScrollBar:vertical {
|
||||
width: 10px;
|
||||
background: transparent;
|
||||
}
|
||||
QScrollBar::handle:vertical {
|
||||
min-height: 30px;
|
||||
background: #202129;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
QScrollBar::handle:vertical:hover {
|
||||
background: rgb(80, 80, 80);
|
||||
}
|
||||
QScrollBar::sub-line:vertical {
|
||||
height: 0px;
|
||||
background: transparent;
|
||||
image: url(:/Black/arrowTop);
|
||||
subcontrol-position: top;
|
||||
}
|
||||
QScrollBar::add-line:vertical {
|
||||
height: 0px;
|
||||
background: transparent;
|
||||
image: url(:/Black/arrowBottom);
|
||||
subcontrol-position: bottom;
|
||||
}
|
||||
QScrollBar::sub-line:vertical:hover {
|
||||
background: rgb(68, 69, 73);
|
||||
}
|
||||
QScrollBar::add-line:vertical:hover {
|
||||
background: rgb(68, 69, 73);
|
||||
}
|
||||
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
||||
background: transparent;
|
||||
}
|
||||
6
res/qss/show.css
Normal file
@@ -0,0 +1,6 @@
|
||||
QWidget{
|
||||
background-color: #1a1a20;
|
||||
color:white;
|
||||
border: 1px solid black;
|
||||
border-bottom: none;
|
||||
}
|
||||
32
res/qss/title.css
Normal file
@@ -0,0 +1,32 @@
|
||||
*{
|
||||
background-color: #202129;
|
||||
border: none;
|
||||
color:white;
|
||||
}
|
||||
|
||||
QPushButton:hover {
|
||||
color: Cyan;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
color: CadetBlue;
|
||||
}
|
||||
|
||||
QPushButton#CloseBtn:hover {
|
||||
color: Tomato;
|
||||
}
|
||||
|
||||
QPushButton#CloseBtn:pressed {
|
||||
color: red;
|
||||
/*background:#FF0000;*/
|
||||
}
|
||||
|
||||
QLabel#MovieNameLab {
|
||||
color: #c4c6d2;
|
||||
}
|
||||
|
||||
/**********提示**********/
|
||||
QToolTip{
|
||||
border: none;
|
||||
background-color: #2e2f37;
|
||||
color:white;
|
||||
}
|
||||
BIN
res/radio_normal.png
Normal file
|
After Width: | Height: | Size: 888 B |
BIN
res/radio_selected.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
res/radiobutton_checked.png
Normal file
|
After Width: | Height: | Size: 777 B |
BIN
res/radiobutton_checked_disable.png
Normal file
|
After Width: | Height: | Size: 928 B |
BIN
res/radiobutton_unchecked.png
Normal file
|
After Width: | Height: | Size: 599 B |
BIN
res/radiobutton_unchecked_disable.png
Normal file
|
After Width: | Height: | Size: 693 B |
34
resource.qrc
Normal file
@@ -0,0 +1,34 @@
|
||||
<RCC>
|
||||
<qresource prefix="/logo">
|
||||
<file>logo.ico</file>
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
<file>res/qss/homewindow.qss</file>
|
||||
<file>res/array_down.png</file>
|
||||
<file>res/checkbox_checked.png</file>
|
||||
<file>res/checkbox_checked_disable.png</file>
|
||||
<file>res/checkbox_parcial.png</file>
|
||||
<file>res/checkbox_parcial_disable.png</file>
|
||||
<file>res/checkbox_unchecked.png</file>
|
||||
<file>res/checkbox_unchecked_disable.png</file>
|
||||
<file>res/radio_normal.png</file>
|
||||
<file>res/radio_selected.png</file>
|
||||
<file>res/radiobutton_checked.png</file>
|
||||
<file>res/radiobutton_checked_disable.png</file>
|
||||
<file>res/radiobutton_unchecked.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/icon">
|
||||
<file>res/icon/fast-backward.png</file>
|
||||
<file>res/icon/fast-forward.png</file>
|
||||
<file>res/icon/next.png</file>
|
||||
<file>res/icon/pause.png</file>
|
||||
<file>res/icon/play.png</file>
|
||||
<file>res/icon/previous.png</file>
|
||||
<file>res/icon/resize.png</file>
|
||||
<file>res/icon/rules.png</file>
|
||||
<file>res/icon/screenshot.png</file>
|
||||
<file>res/icon/stop-button.png</file>
|
||||
<file>res/icon/tune.png</file>
|
||||
<file>res/icon/quick.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
177
screenshot.cpp
Normal file
@@ -0,0 +1,177 @@
|
||||
#include "screenshot.h"
|
||||
#include "easylogging++.h"
|
||||
AVFrame *allocate_sws_frame(AVCodecContext *enc_ctx)
|
||||
{
|
||||
int ret = 0;
|
||||
AVFrame *sws_frame = av_frame_alloc();
|
||||
if(sws_frame)
|
||||
{
|
||||
sws_frame->format = enc_ctx->pix_fmt;
|
||||
sws_frame->width = enc_ctx->width;
|
||||
sws_frame->height = enc_ctx->height;
|
||||
sws_frame->pict_type = AV_PICTURE_TYPE_NONE;
|
||||
ret = av_frame_get_buffer(sws_frame, 32); // 分配buffer
|
||||
if(ret <0)
|
||||
{
|
||||
av_frame_free(&sws_frame);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return sws_frame;
|
||||
}
|
||||
|
||||
ScreenShot::ScreenShot()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int ScreenShot::SaveJpeg(AVFrame *src_frame, const char *file_name, int jpeg_quality)
|
||||
{
|
||||
AVFormatContext* ofmt_ctx = NULL;
|
||||
AVOutputFormat* fmt = NULL;
|
||||
AVStream* video_st = NULL;
|
||||
AVCodecContext* enc_ctx = NULL;
|
||||
AVCodec* codec = NULL;
|
||||
AVFrame* picture = NULL;
|
||||
AVPacket *pkt = NULL;
|
||||
int got_picture = 0;
|
||||
int ret = 0;
|
||||
struct SwsContext *img_convert_ctx = NULL;
|
||||
|
||||
ofmt_ctx = avformat_alloc_context();
|
||||
//Guess format
|
||||
fmt = av_guess_format("mjpeg", NULL, NULL);
|
||||
ofmt_ctx->oformat = fmt;
|
||||
//Output URL
|
||||
if (avio_open(&ofmt_ctx->pb, file_name, AVIO_FLAG_READ_WRITE) < 0){
|
||||
LOG(ERROR) <<"Couldn't open output file.";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
video_st = avformat_new_stream(ofmt_ctx, 0);
|
||||
if (video_st==NULL){
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
enc_ctx = video_st->codec;
|
||||
enc_ctx->codec_id = AV_CODEC_ID_MJPEG; // mjpeg支持的编码器
|
||||
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
enc_ctx->pix_fmt = AV_PIX_FMT_YUVJ420P; // AV_CODEC_ID_MJPEG 支持的像素格式
|
||||
|
||||
enc_ctx->width = src_frame->width;
|
||||
enc_ctx->height = src_frame->height;
|
||||
|
||||
enc_ctx->time_base.num = 1;
|
||||
enc_ctx->time_base.den = 25;
|
||||
//Output some information
|
||||
av_dump_format(ofmt_ctx, 0, file_name, 1);
|
||||
|
||||
codec = avcodec_find_encoder(enc_ctx->codec_id);
|
||||
if (!codec){
|
||||
LOG(ERROR) << "jpeg Codec not found.";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
if (avcodec_open2(enc_ctx, codec,NULL) < 0){
|
||||
LOG(ERROR) << "Could not open jpeg codec.";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
ret = avcodec_parameters_from_context(video_st->codecpar, enc_ctx);
|
||||
if(ret < 0) {
|
||||
LOG(ERROR) << "avcodec_parameters_from_context failed";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
if(src_frame->format != enc_ctx->pix_fmt) {
|
||||
img_convert_ctx = sws_getContext(enc_ctx->width, enc_ctx->height,
|
||||
(enum AVPixelFormat)src_frame->format, enc_ctx->width, enc_ctx->height,
|
||||
enc_ctx->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
if (!img_convert_ctx) {
|
||||
LOG(ERROR) << "Impossible to create scale context for the conversion fmt:"
|
||||
<< av_get_pix_fmt_name((enum AVPixelFormat)src_frame->format)
|
||||
<< ", s:" << enc_ctx->width << "x" << enc_ctx->height << " -> fmt:" << av_get_pix_fmt_name(enc_ctx->pix_fmt)
|
||||
<< ", s:" << enc_ctx->width << "x" << enc_ctx->height ;
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if(jpeg_quality > 0)
|
||||
{
|
||||
if(jpeg_quality > 100)
|
||||
jpeg_quality = 100;
|
||||
|
||||
enc_ctx->qcompress = (float)jpeg_quality/100.f; // 0~1.0, default is 0.5
|
||||
enc_ctx->qmin = 2;
|
||||
enc_ctx->qmax = 31;
|
||||
enc_ctx->max_qdiff = 3;
|
||||
|
||||
LOG(ERROR) <<"JPEG quality is: %d" << jpeg_quality;
|
||||
}
|
||||
pkt = av_packet_alloc();
|
||||
//Write Header
|
||||
ret = avformat_write_header(ofmt_ctx, NULL);
|
||||
if(ret < 0) {
|
||||
LOG(ERROR) <<"avformat_write_header failed";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if(img_convert_ctx) // 如果需要转换pix_fmt
|
||||
{
|
||||
// 分配转换后的frame
|
||||
picture = allocate_sws_frame(enc_ctx);
|
||||
/* make sure the frame data is writable */
|
||||
ret = av_frame_make_writable(picture);
|
||||
ret = sws_scale(img_convert_ctx, (const uint8_t **) src_frame->data, src_frame->linesize, 0, src_frame->height,
|
||||
picture->data, picture->linesize);
|
||||
picture->pts = 0;
|
||||
ret = avcodec_encode_video2(enc_ctx, pkt, picture, &got_picture);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = avcodec_encode_video2(enc_ctx, pkt, src_frame, &got_picture);
|
||||
}
|
||||
|
||||
if(ret < 0){
|
||||
LOG(ERROR) <<"avcodec_encode_video2 Error.";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
if (got_picture==1){
|
||||
pkt->stream_index = video_st->index;
|
||||
ret = av_write_frame(ofmt_ctx, pkt);
|
||||
if(ret < 0) {
|
||||
LOG(ERROR) <<"av_write_frame Error.";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
}else {
|
||||
LOG(ERROR) <<"no got_picture";
|
||||
ret = -1;
|
||||
goto fail;
|
||||
}
|
||||
ret = 0;
|
||||
fail:
|
||||
//Write Trailer
|
||||
ret = av_write_trailer(ofmt_ctx);
|
||||
if(ret < 0)
|
||||
LOG(ERROR) <<"av_write_trailer Error.";
|
||||
if(pkt)
|
||||
av_packet_free(&pkt);
|
||||
if (enc_ctx)
|
||||
avcodec_close(enc_ctx);
|
||||
if(picture)
|
||||
av_frame_free(&picture);
|
||||
if(ofmt_ctx && ofmt_ctx->pb)
|
||||
avio_close(ofmt_ctx->pb);
|
||||
if(ofmt_ctx)
|
||||
avformat_free_context(ofmt_ctx);
|
||||
if(img_convert_ctx)
|
||||
sws_freeContext(img_convert_ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
32
screenshot.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef SCREENSHOT_H
|
||||
#define SCREENSHOT_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avio.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "libswscale/swscale.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
//#include <strings.h>
|
||||
#include <stdio.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
class ScreenShot
|
||||
{
|
||||
public:
|
||||
ScreenShot();
|
||||
/**
|
||||
* @brief SaveJpeg 将frame保存位jpeg图片
|
||||
* @param src_frame 要保存的帧
|
||||
* @param file_name 保存的图片路径
|
||||
* @param jpeg_quality 图片质量
|
||||
* @return
|
||||
*/
|
||||
int SaveJpeg(AVFrame *src_frame, const char* file_name, int jpeg_quality);
|
||||
};
|
||||
|
||||
#endif // SCREENSHOT_H
|
||||
18
setting.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "setting.h"
|
||||
#include "ui_setting.h"
|
||||
|
||||
Setting::Setting(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, ui(new Ui::Setting)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->audioBufEdit->setText("0ms");
|
||||
ui->videoBufEdit->setText("0ms");
|
||||
ui->bufDurationBox->setCurrentIndex(3);
|
||||
ui->jitterBufBox->setCurrentIndex(1);
|
||||
}
|
||||
|
||||
Setting::~Setting()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
22
setting.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef SETTING_H
|
||||
#define SETTING_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class Setting;
|
||||
}
|
||||
|
||||
class Setting : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Setting(QWidget *parent = nullptr);
|
||||
~Setting();
|
||||
|
||||
private:
|
||||
Ui::Setting *ui;
|
||||
};
|
||||
|
||||
#endif // SETTING_H
|
||||
269
setting.ui
Normal file
@@ -0,0 +1,269 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Setting</class>
|
||||
<widget class="QWidget" name="Setting">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>80</x>
|
||||
<y>90</y>
|
||||
<width>274</width>
|
||||
<height>79</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="audioBufEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>音频缓存</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>视频缓存</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QComboBox" name="jitterBufBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>30ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>100ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>200ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>400ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>600ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>800ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1000ms</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="videoBufEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>70</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>视频缓存</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QComboBox" name="bufDurationBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>30ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>100ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>200ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>400ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>600ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>800ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1000ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>2000ms</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>4000ms</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>50</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>音频缓存</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>缓存阈值</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>抖动值</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
164
sonic.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/* Sonic library
|
||||
Copyright 2010
|
||||
Bill Cox
|
||||
This file is part of the Sonic Library.
|
||||
|
||||
This file is licensed under the Apache 2.0 license, and also placed into the public domain.
|
||||
Use it either way, at your option.
|
||||
*/
|
||||
|
||||
/*
|
||||
The Sonic Library implements a new algorithm invented by Bill Cox for the
|
||||
specific purpose of speeding up speech by high factors at high quality. It
|
||||
generates smooth speech at speed up factors as high as 6X, possibly more. It is
|
||||
also capable of slowing down speech, and generates high quality results
|
||||
regardless of the speed up or slow down factor. For speeding up speech by 2X or
|
||||
more, the following equation is used:
|
||||
|
||||
newSamples = period/(speed - 1.0)
|
||||
scale = 1.0/newSamples;
|
||||
|
||||
where period is the current pitch period, determined using AMDF or any other
|
||||
pitch estimator, and speed is the speedup factor. If the current position in
|
||||
the input stream is pointed to by "samples", and the current output stream
|
||||
position is pointed to by "out", then newSamples number of samples can be
|
||||
generated with:
|
||||
|
||||
out[t] = (samples[t]*(newSamples - t) + samples[t + period]*t)/newSamples;
|
||||
|
||||
where t = 0 to newSamples - 1.
|
||||
|
||||
For speed factors < 2X, the PICOLA algorithm is used. The above
|
||||
algorithm is first used to double the speed of one pitch period. Then, enough
|
||||
input is directly copied from the input to the output to achieve the desired
|
||||
speed up factor, where 1.0 < speed < 2.0. The amount of data copied is derived:
|
||||
|
||||
speed = (2*period + length)/(period + length)
|
||||
speed*length + speed*period = 2*period + length
|
||||
length(speed - 1) = 2*period - speed*period
|
||||
length = period*(2 - speed)/(speed - 1)
|
||||
|
||||
For slowing down speech where 0.5 < speed < 1.0, a pitch period is inserted into
|
||||
the output twice, and length of input is copied from the input to the output
|
||||
until the output desired speed is reached. The length of data copied is:
|
||||
|
||||
length = period*(speed - 0.5)/(1 - speed)
|
||||
|
||||
For slow down factors below 0.5, no data is copied, and an algorithm
|
||||
similar to high speed factors is used.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Uncomment this to use sin-wav based overlap add which in theory can improve
|
||||
sound quality slightly, at the expense of lots of floating point math. */
|
||||
/* #define SONIC_USE_SIN */
|
||||
|
||||
/* This specifies the range of voice pitches we try to match.
|
||||
Note that if we go lower than 65, we could overflow in findPitchInRange */
|
||||
#define SONIC_MIN_PITCH 65
|
||||
#define SONIC_MAX_PITCH 400
|
||||
|
||||
/* These are used to down-sample some inputs to improve speed */
|
||||
#define SONIC_AMDF_FREQ 4000
|
||||
|
||||
struct sonicStreamStruct;
|
||||
typedef struct sonicStreamStruct *sonicStream;
|
||||
|
||||
/* For all of the following functions, numChannels is multiplied by numSamples
|
||||
to determine the actual number of values read or returned. */
|
||||
|
||||
/* Create a sonic stream. Return NULL only if we are out of memory and cannot
|
||||
allocate the stream. Set numChannels to 1 for mono, and 2 for stereo. */
|
||||
// 创建一个音频流,如果内存溢出不能创建流会返回NULL,numCHannels表示声道的个数,1为单声道,2为双声道
|
||||
sonicStream sonicCreateStream(int sampleRate, int numChannels);
|
||||
/* Destroy the sonic stream. */
|
||||
// 销毁一个音频流
|
||||
void sonicDestroyStream(sonicStream stream);
|
||||
/* Use this to write floating point data to be speed up or down into the stream.
|
||||
Values must be between -1 and 1. Return 0 if memory realloc failed, otherwise 1 */
|
||||
//
|
||||
int sonicWriteFloatToStream(sonicStream stream, float *samples, int numSamples);
|
||||
/* Use this to write 16-bit data to be speed up or down into the stream.
|
||||
Return 0 if memory realloc failed, otherwise 1 */
|
||||
int sonicWriteShortToStream(sonicStream stream, short *samples, int numSamples);
|
||||
/* Use this to write 8-bit unsigned data to be speed up or down into the stream.
|
||||
Return 0 if memory realloc failed, otherwise 1 */
|
||||
int sonicWriteUnsignedCharToStream(sonicStream stream, unsigned char *samples, int numSamples);
|
||||
/* Use this to read floating point data out of the stream. Sometimes no data
|
||||
will be available, and zero is returned, which is not an error condition. */
|
||||
int sonicReadFloatFromStream(sonicStream stream, float *samples, int maxSamples);
|
||||
/* Use this to read 16-bit data out of the stream. Sometimes no data will
|
||||
be available, and zero is returned, which is not an error condition. */
|
||||
int sonicReadShortFromStream(sonicStream stream, short *samples, int maxSamples);
|
||||
/* Use this to read 8-bit unsigned data out of the stream. Sometimes no data will
|
||||
be available, and zero is returned, which is not an error condition. */
|
||||
int sonicReadUnsignedCharFromStream(sonicStream stream, unsigned char *samples, int maxSamples);
|
||||
/* Force the sonic stream to generate output using whatever data it currently
|
||||
has. No extra delay will be added to the output, but flushing in the middle of
|
||||
words could introduce distortion. */
|
||||
// 立即强制刷新流
|
||||
int sonicFlushStream(sonicStream stream);
|
||||
/* Return the number of samples in the output buffer */
|
||||
// 返回输出缓冲中的采样点数目
|
||||
int sonicSamplesAvailable(sonicStream stream);
|
||||
/* Get the speed of the stream. */
|
||||
// 得到音频流的速度
|
||||
float sonicGetSpeed(sonicStream stream);
|
||||
/* Set the speed of the stream. */
|
||||
// 设置音频流的速度
|
||||
void sonicSetSpeed(sonicStream stream, float speed);
|
||||
/* Get the pitch of the stream. */
|
||||
float sonicGetPitch(sonicStream stream);
|
||||
/* Set the pitch of the stream. */
|
||||
void sonicSetPitch(sonicStream stream, float pitch);
|
||||
/* Get the rate of the stream. */
|
||||
float sonicGetRate(sonicStream stream);
|
||||
/* Set the rate of the stream. */
|
||||
void sonicSetRate(sonicStream stream, float rate);
|
||||
/* Get the scaling factor of the stream. */
|
||||
float sonicGetVolume(sonicStream stream);
|
||||
/* Set the scaling factor of the stream. */
|
||||
void sonicSetVolume(sonicStream stream, float volume);
|
||||
/* Get the chord pitch setting. */
|
||||
int sonicGetChordPitch(sonicStream stream);
|
||||
/* Set chord pitch mode on or off. Default is off. See the documentation
|
||||
page for a description of this feature. */
|
||||
void sonicSetChordPitch(sonicStream stream, int useChordPitch);
|
||||
/* Get the quality setting. */
|
||||
// 得到音频流的质量
|
||||
int sonicGetQuality(sonicStream stream);
|
||||
/* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */
|
||||
// 设置音频流的质量,默认的0的质量几乎和1的一样好,但是更快
|
||||
void sonicSetQuality(sonicStream stream, int quality);
|
||||
/* Get the sample rate of the stream. */
|
||||
// 得到音频流的采样率
|
||||
int sonicGetSampleRate(sonicStream stream);
|
||||
/* Set the sample rate of the stream. This will drop any samples that have not been read. */
|
||||
// 设置音频流的采样率
|
||||
void sonicSetSampleRate(sonicStream stream, int sampleRate);
|
||||
/* Get the number of channels. */
|
||||
// 得到音频的声道数
|
||||
int sonicGetNumChannels(sonicStream stream);
|
||||
/* Set the number of channels. This will drop any samples that have not been read. */
|
||||
// 设置音频流的声道数
|
||||
void sonicSetNumChannels(sonicStream stream, int numChannels);
|
||||
/* This is a non-stream oriented interface to just change the speed of a sound
|
||||
sample. It works in-place on the sample array, so there must be at least
|
||||
speed*numSamples available space in the array. Returns the new number of samples. */
|
||||
// 这是一个非面向流的借口,只是改变声音采样的速率。它工作在采样数组内部,
|
||||
//所以在数组内至少要有speed*numSampes大小的空间。返回值是新的采样点的数目
|
||||
|
||||
int sonicChangeFloatSpeed(float *samples, int numSamples, float speed, float pitch,
|
||||
float rate, float volume, int useChordPitch, int sampleRate, int numChannels);
|
||||
/* This is a non-stream oriented interface to just change the speed of a sound
|
||||
sample. It works in-place on the sample array, so there must be at least
|
||||
speed*numSamples available space in the array. Returns the new number of samples. */
|
||||
int sonicChangeShortSpeed(short *samples, int numSamples, float speed, float pitch,
|
||||
float rate, float volume, int useChordPitch, int sampleRate, int numChannels);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
91
toast.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "toast.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QEvent>
|
||||
|
||||
class ToastDlg: public QDialog
|
||||
{
|
||||
private:
|
||||
QLabel* mLabel;
|
||||
QLabel* mCloseBtn;
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *ev) override
|
||||
{
|
||||
if (obj == mCloseBtn) {
|
||||
if (ev->type() == QEvent::MouseButtonRelease) {
|
||||
accept();
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(obj, ev);
|
||||
}
|
||||
public:
|
||||
ToastDlg()
|
||||
{
|
||||
auto layout = new QHBoxLayout;//水平布局
|
||||
mLabel = new QLabel;
|
||||
mLabel->setStyleSheet("color: white; background:transparent");//red
|
||||
layout->addWidget(mLabel, 1);//stretch = 1
|
||||
mCloseBtn = new QLabel;
|
||||
//mCloseBtn->setPixmap(QPixmap(":/res/img/close.png"));
|
||||
mCloseBtn->installEventFilter(this);
|
||||
mCloseBtn->setStyleSheet("background:transparent");
|
||||
layout->addWidget(mCloseBtn);
|
||||
setLayout(layout);
|
||||
setWindowFlag(Qt::FramelessWindowHint);//生成一个无边界窗口。用户不能通过窗口系统移动或调整无边界窗口的大小。
|
||||
setAttribute(Qt::WA_ShowWithoutActivating, true); //Show the widget without making it active.
|
||||
//setAttribute(Qt::WA_TranslucentBackground, true); // 背景透明
|
||||
}
|
||||
|
||||
void show(Toast::Level level, const QString& text)
|
||||
{
|
||||
QPalette p = palette();
|
||||
//QColor(int r, int g, int b, int a = 255)
|
||||
//Constructs a color with the RGB value r, g, b, and the alpha-channel (transparency) value of a.
|
||||
p.setColor(QPalette::Window, QColor(0, 0, 0, 200));
|
||||
if (level == Toast::INFO) {
|
||||
p.setColor(QPalette::Window, QColor(0x35, 0x79, 0xd5, 0x88));// 蓝色
|
||||
} else if (level == Toast::WARN) {
|
||||
p.setColor(QPalette::Window, QColor(0xff, 0xff, 0x33, 0x88));
|
||||
} else { //ERROR
|
||||
p.setColor(QPalette::Window, QColor(0xff, 0x0, 0x0, 0x88));
|
||||
}
|
||||
setPalette(p);//set widget's palette
|
||||
mLabel->setText(text);
|
||||
setWindowFlag(Qt::WindowStaysOnTopHint);//通知窗口系统该窗口应保持在所有其他窗口的顶部。
|
||||
QDialog::show();
|
||||
}
|
||||
};//~class ToastDlg end
|
||||
|
||||
|
||||
Toast::Toast()//构造函数
|
||||
{
|
||||
dlg_ = new ToastDlg;
|
||||
}
|
||||
|
||||
//返回一个实例(instance)
|
||||
Toast &Toast::instance()
|
||||
{
|
||||
static Toast thiz;//这种实例化方法会自动回收内存
|
||||
return thiz;
|
||||
}
|
||||
|
||||
void Toast::show(Toast::Level level, const QString &text)
|
||||
{
|
||||
dlg_->show(level, text);//ToastDlg.show方法
|
||||
if (timer_id_ != 0) { //int mTimerId
|
||||
//如果之前已经开启了一个定时器,先把他关掉
|
||||
killTimer(timer_id_);
|
||||
}
|
||||
timer_id_ = startTimer(5000);//启动定时器,每2s触发定时器事件,直到调用killTimer
|
||||
}
|
||||
|
||||
//重写定时器事件回调函数
|
||||
void Toast::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
killTimer(timer_id_);
|
||||
timer_id_ = 0;
|
||||
dlg_->accept();//隐藏模态对话框
|
||||
//mDlg->hide();
|
||||
}
|
||||
35
toast.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef TOAST_H
|
||||
#define TOAST_H
|
||||
/**
|
||||
* @brief The Toast class
|
||||
* 源码参考:Qt实现Toast提示消息 https://blog.csdn.net/wwwlyj123321/article/details/112391884
|
||||
*/
|
||||
|
||||
|
||||
#include <QObject>
|
||||
#include <QRect>
|
||||
class ToastDlg;
|
||||
|
||||
class Toast: QObject
|
||||
{
|
||||
public:
|
||||
enum Level
|
||||
{
|
||||
INFO, WARN, ERROR
|
||||
};
|
||||
private:
|
||||
Toast();
|
||||
public:
|
||||
static Toast& instance();
|
||||
public:
|
||||
void show(Level level, const QString& text);
|
||||
|
||||
private:
|
||||
void timerEvent(QTimerEvent *event) override;
|
||||
|
||||
private:
|
||||
ToastDlg* dlg_;
|
||||
int timer_id_{0};
|
||||
QRect geometry_;
|
||||
};
|
||||
#endif // TOAST_H
|
||||
18
urldialog.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "urldialog.h"
|
||||
#include "ui_urldialog.h"
|
||||
|
||||
UrlDialog::UrlDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::UrlDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
UrlDialog::~UrlDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
QString UrlDialog::GetUrl()
|
||||
{
|
||||
return ui->urlLineEdit->text();
|
||||
}
|
||||
22
urldialog.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef URLDIALOG_H
|
||||
#define URLDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace Ui {
|
||||
class UrlDialog;
|
||||
}
|
||||
|
||||
class UrlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UrlDialog(QWidget *parent = 0);
|
||||
~UrlDialog();
|
||||
QString GetUrl();
|
||||
private:
|
||||
Ui::UrlDialog *ui;
|
||||
};
|
||||
|
||||
#endif // URLDIALOG_H
|
||||
91
urldialog.ui
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>UrlDialog</class>
|
||||
<widget class="QDialog" name="UrlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>653</width>
|
||||
<height>157</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>220</x>
|
||||
<y>100</y>
|
||||
<width>301</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="urlLineEdit">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>30</x>
|
||||
<y>50</y>
|
||||
<width>591</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>20</y>
|
||||
<width>231</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>输入网络地址,比如http/rtmp</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>UrlDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>UrlDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
7
util.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
|
||||
|
||||
//void ffmpeg_err2str(int err, char *)
|
||||
|
||||
#endif // UTIL_H
|
||||