This commit is contained in:
2025-09-25 16:56:53 +08:00
parent e44678d519
commit 1d9da56656
88 changed files with 18171 additions and 0 deletions

61
CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

234
ff_ffplay.h Normal file
View 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
View 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
View 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; // 一秒时间的字节数比如采样率48Khz2 channel16bit则一秒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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
#include "ijksdl_timer.h"
ijksdl_timer::ijksdl_timer()
{
}

20
ijksdl_timer.h Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

4576
log/easylogging++.h Normal file

File diff suppressed because it is too large Load Diff

BIN
logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

33
main.cpp Normal file
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

275
playlist.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
res/checkbox_checked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

BIN
res/checkbox_parcial.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

BIN
res/checkbox_unchecked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

BIN
res/fa-solid-900.ttf Normal file

Binary file not shown.

BIN
res/fontawesome-webfont.ttf Normal file

Binary file not shown.

BIN
res/icon/fast-backward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
res/icon/fast-forward.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
res/icon/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
res/icon/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
res/icon/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
res/icon/previous.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
res/icon/quick.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
res/icon/resize.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
res/icon/rules.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
res/icon/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
res/icon/stop-button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
res/icon/tune.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

48
res/qss/about.css Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 B

BIN
res/radio_selected.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
res/radiobutton_checked.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 928 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

34
resource.qrc Normal file
View 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
View 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
View 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
View 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
View 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
View 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>

1356
sonic.cpp Normal file

File diff suppressed because it is too large Load Diff

164
sonic.h Normal file
View 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. */
// 创建一个音频流如果内存溢出不能创建流会返回NULLnumCHannels表示声道的个数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
View 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
View 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
View 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
View 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
View 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>

3
util.cpp Normal file
View File

@@ -0,0 +1,3 @@
#include "util.h"

7
util.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef UTIL_H
#define UTIL_H
//void ffmpeg_err2str(int err, char *)
#endif // UTIL_H

BIN
播放器.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB