teleport/client/tp-player/thr_data.cpp

471 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QNetworkCookie>
#include <QStandardPaths>
#include <qcoreapplication.h>
#include <inttypes.h>
#include "thr_play.h"
#include "thr_data.h"
#include "util.h"
#include "downloader.h"
#include "record_format.h"
#include "mainwindow.h"
//=================================================================
// ThrData
//=================================================================
ThrData::ThrData(MainWindow* mainwin, const QString& res) {
m_mainwin = mainwin;
m_res = res;
m_need_download = false;
m_need_stop = false;
// m_dl = nullptr;
#ifdef __APPLE__
m_data_path_base = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
m_data_path_base += "/tp-testdata/";
#else
m_data_path_base = QCoreApplication::applicationDirPath() + "/record";
#endif
qDebug("data-path-base: %s", m_data_path_base.toStdString().c_str());
// qDebug() << "AppConfigLocation:" << QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
// qDebug() << "AppDataLocation:" << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
// qDebug() << "AppLocalDataLocation:" << QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
// qDebug() << "ConfigLocation:" << QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
// qDebug() << "CacheLocation:" << QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
// qDebug() << "GenericCacheLocation:" << QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
/*
AppConfigLocation: "C:/Users/apex/AppData/Local/tp-player"
AppDataLocation: "C:/Users/apex/AppData/Roaming/tp-player"
AppLocalDataLocation: "C:/Users/apex/AppData/Local/tp-player"
ConfigLocation: "C:/Users/apex/AppData/Local/tp-player"
CacheLocation: "C:/Users/apex/AppData/Local/tp-player/cache"
GenericCacheLocation: "C:/Users/apex/AppData/Local/cache"
*/
}
ThrData::~ThrData() {
_clear_data();
}
void ThrData::stop() {
if(!isRunning())
return;
m_need_stop = true;
wait();
qDebug("data thread stop() end.");
}
void ThrData::_notify_message(const QString& msg) {
UpdateData* _msg = new UpdateData(TYPE_MESSAGE);
_msg->message(msg);
emit signal_update_data(_msg);
}
void ThrData::_notify_error(const QString& msg) {
UpdateData* _msg = new UpdateData(TYPE_ERROR);
_msg->message(msg);
emit signal_update_data(_msg);
}
void ThrData::run() {
_run();
qDebug("ThrData thread run() end.");
}
void ThrData::_run() {
QString _tmp_res = m_res.toLower();
if(_tmp_res.startsWith("http")) {
m_need_download = true;
_notify_message(LOCAL8BIT("正在准备录像数据,请稍候..."));
if(!m_thr_download.init(m_data_path_base, m_res)) {
_notify_error(QString("%1\n\n%2").arg(LOCAL8BIT("无法下载录像文件!\n\n"), m_res));
return;
}
m_thr_download.start();
msleep(100);
for(;;) {
if(m_need_stop)
return;
if(!m_thr_download.is_running() || m_thr_download.is_tpk_downloaded())
break;
msleep(100);
}
if(!m_thr_download.is_tpk_downloaded())
return;
m_thr_download.get_data_path(m_data_path);
}
else {
QFileInfo fi_chk_link(m_res);
if(fi_chk_link.isSymLink())
m_res = fi_chk_link.symLinkTarget();
QFileInfo fi(m_res);
if(!fi.exists()) {
_notify_error(QString("%1\n\n%2").arg(LOCAL8BIT("指定的文件或目录不存在!"), m_res));
return;
}
if(fi.isFile()) {
m_data_path = fi.path();
}
else if(fi.isDir()) {
m_data_path = m_res;
}
m_data_path = QDir::toNativeSeparators(m_data_path);
}
// 到这里,.tpr和.tpk文件均已经下载完成了。
if(!_load_header())
return;
if(!_load_keyframe())
return;
UpdateData* dat = new UpdateData(m_hdr);
// dat->alloc_data(sizeof(TS_RECORD_HEADER));
// memcpy(dat->data_buf(), &m_hdr, sizeof(TS_RECORD_HEADER));
emit signal_update_data(dat);
/*
// fake-code:
file_index = 0;
for(;;) {
if(file_index >= file_count) {
msleep(500);
continue;
}
if(queue.size < 500) {
need_pkg = 1000 - queue.size;
for(i = 0; i < need_pkg; i++) {
if(f.not_open) {
f.open(file_index);
}
pkg = read_pkg()
if(f.to_end) {
f.close();
file_index += 1;
if(file_index >= file_count)
break;
}
queue.add(pkg)
}
if(file_index >= file_count)
break;
}
else {
msleep(100);
}
}
*/
QFile* fdata = nullptr;
uint32_t file_idx = 0;
uint32_t start_offset = 0;
qint64 file_size = 0;
qint64 file_processed = 0;
qint64 read_len = 0;
QString str_fidx;
for(;;) {
// 任何时候确保第一时间响应退出操作
if(m_need_stop)
return;
// 如果所有文件都已经处理完了,则等待(可能用户会拖动滚动条,或者重新播放)
if(file_idx >= m_hdr.info.dat_file_count) {
msleep(500);
continue;
}
// 看看待播放队列中还有多少个数据包
int pkg_count_in_queue = 0;
int pkg_need_add = 0;
m_locker.lock();
pkg_count_in_queue = m_data.size();
m_locker.unlock();
// 少于500个的话补足到1000个
if(m_data.size() < 500)
pkg_need_add = 1000 - pkg_count_in_queue;
if(pkg_need_add == 0) {
msleep(100);
continue;
}
for(int i = 0; i < pkg_need_add; ++i) {
if(m_need_stop)
return;
// 如果数据文件尚未打开,则打开它
if(fdata == nullptr) {
str_fidx.sprintf("%d", file_idx+1);
QString tpd_fname = QString("%1/tp-rdp-%2.tpd").arg(m_data_path, str_fidx);
tpd_fname = QDir::toNativeSeparators(tpd_fname);
QFileInfo fi_tpd(tpd_fname);
if(!fi_tpd.exists()) {
if(m_need_download) {
// 此文件尚未下载完成,等待
for(;;) {
if(m_need_stop)
return;
if(!m_thr_download.is_running() || m_thr_download.is_tpd_downloaded(file_idx))
break;
msleep(100);
}
// 下载失败了
if(!m_thr_download.is_tpd_downloaded(file_idx))
return;
}
}
fdata = new QFile(tpd_fname);
if(!fdata->open(QFile::ReadOnly)) {
qDebug() << "Can not open " << tpd_fname << " for read.";
_notify_error(QString("%1\n\n%2").arg(LOCAL8BIT("无法打开录像数据文件!"), tpd_fname));
return;
}
file_size = fdata->size();
file_processed = 0;
qDebug("Open file, processed: %" PRId64 ", size: %" PRId64, file_processed, file_size);
}
// qDebug("B processed: %" PRId64 ", size: %" PRId64, file_processed, file_size);
//----------------------------------
// 读取一个数据包
//----------------------------------
if(file_size - file_processed < sizeof(TS_RECORD_PKG)) {
qDebug("invaid tp-rdp-%d.tpd file, filesize=%" PRId64 ", processed=%" PRId64 ", need=%d.", file_idx+1, file_size, file_processed, sizeof(TS_RECORD_PKG));
_notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
return;
}
TS_RECORD_PKG pkg;
read_len = fdata->read(reinterpret_cast<char*>(&pkg), sizeof(TS_RECORD_PKG));
// if(read_len == 0)
// break;
if(read_len != sizeof(TS_RECORD_PKG)) {
qDebug("invaid tp-rdp-%d.tpd file, read_len=%" PRId64 " (1).", file_idx+1, read_len);
_notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
return;
}
file_processed += sizeof(TS_RECORD_PKG);
if(file_size - file_processed < pkg.size) {
qDebug("invaid tp-rdp-%d.tpd file (2).", file_idx+1);
_notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
return;
}
if(pkg.size == 0) {
qDebug("################## too bad.");
}
QByteArray pkg_data = fdata->read(pkg.size);
if(pkg_data.size() != pkg.size) {
qDebug("invaid tp-rdp-%d.tpd file, read_len=%" PRId64 " (3).", file_idx+1, read_len);
_notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
return;
}
file_processed += pkg.size;
UpdateData* dat = new UpdateData();
if(!dat->parse(pkg, pkg_data)) {
qDebug("invaid tp-rdp-%d.tpd file (4).", file_idx+1);
_notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
return;
}
// UpdateData* dat = new UpdateData(TYPE_DATA);
// dat->alloc_data(sizeof(TS_RECORD_PKG) + pkg.size);
// memcpy(dat->data_buf(), &pkg, sizeof(TS_RECORD_PKG));
// read_len = fdata->read(reinterpret_cast<char*>(dat->data_buf()+sizeof(TS_RECORD_PKG)), pkg.size);
// if(read_len != pkg.size) {
// delete dat;
// qDebug("invaid tp-rdp-%d.tpd file, read_len=%" PRId64 " (3).", file_idx+1, read_len);
// _notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx));
// return;
// }
// file_processed += pkg.size;
// 跳过关键帧
// TODO: 拖动滚动条后,需要显示一次关键帧数据,然后跳过后续关键帧。
if(pkg.type == TS_RECORD_TYPE_RDP_KEYFRAME) {
qDebug("----key frame: %ld, processed=%" PRId64 ", pkg.size=%d", pkg.time_ms, file_processed, pkg.size);
delete dat;
dat = nullptr;
}
// 数据放到待播放列表中
if(dat) {
m_locker.lock();
m_data.enqueue(dat);
// qDebug("queue data count: %d", m_data.size());
m_locker.unlock();
}
msleep(1);
// 如果此文件已经处理完毕,则关闭文件,这样下次处理一个新的文件
// qDebug("C processed: %" PRId64 ", size: %" PRId64, file_processed, file_size);
if(file_processed >= file_size) {
fdata->close();
delete fdata;
fdata = nullptr;
file_idx++;
}
if(file_idx >= m_hdr.info.dat_file_count) {
UpdateData* dat = new UpdateData(TYPE_END);
m_locker.lock();
m_data.enqueue(dat);
// qDebug("queue data count: %d", m_data.size());
m_locker.unlock();
}
}
}
}
bool ThrData::_load_header() {
QString msg;
qDebug() << "PATH_BASE: " << m_data_path;
QString filename = QString("%1/tp-rdp.tpr").arg(m_data_path);
filename = QDir::toNativeSeparators(filename);
qDebug() << "TPR: " << filename;
QFile f(filename);
if(!f.open(QFile::ReadOnly)) {
qDebug() << "Can not open " << filename << " for read.";
_notify_error(QString("%1\n\n%2").arg(LOCAL8BIT("无法打开录像信息文件!"), filename));
return false;
}
memset(&m_hdr, 0, sizeof(TS_RECORD_HEADER));
qint64 read_len = 0;
read_len = f.read(reinterpret_cast<char*>(&m_hdr), sizeof(TS_RECORD_HEADER));
if(read_len != sizeof(TS_RECORD_HEADER)) {
qDebug() << "invaid .tpr file.";
_notify_error(QString("%1\n\n%2").arg(LOCAL8BIT("错误的录像信息文件!"), filename));
return false;
}
if(m_hdr.info.ver != 4) {
qDebug() << "invaid .tpr file.";
_notify_error(QString("%1 %2%3").arg(LOCAL8BIT("不支持的录像文件版本 "), QString(m_hdr.info.ver), LOCAL8BIT("\n\n此播放器支持录像文件版本 4。")));
return false;
}
if(m_hdr.basic.width == 0 || m_hdr.basic.height == 0) {
_notify_error(LOCAL8BIT("错误的录像信息,未记录窗口尺寸!"));
return false;
}
if(m_hdr.info.dat_file_count == 0) {
_notify_error(LOCAL8BIT("错误的录像信息,未记录数据文件数量!"));
return false;
}
return true;
}
bool ThrData::_load_keyframe() {
QString tpk_fname = QString("%1/tp-rdp.tpk").arg(m_data_path);
tpk_fname = QDir::toNativeSeparators(tpk_fname);
qDebug() << "TPK: " << tpk_fname;
QFile f_kf(tpk_fname);
if(!f_kf.open(QFile::ReadOnly)) {
qDebug() << "Can not open " << tpk_fname << " for read.";
_notify_error(QString("%1\n\n%3").arg(LOCAL8BIT("无法打开关键帧信息文件!"), tpk_fname));
return false;
}
qint64 fsize = f_kf.size();
if(!fsize || fsize % sizeof(KEYFRAME_INFO) != 0) {
qDebug() << "Can not open " << tpk_fname << " for read.";
_notify_error(LOCAL8BIT("关键帧信息文件格式错误!"));
return false;
}
qint64 read_len = 0;
int kf_count = fsize / sizeof(KEYFRAME_INFO);
for(int i = 0; i < kf_count; ++i) {
KEYFRAME_INFO kf;
memset(&kf, 0, sizeof(KEYFRAME_INFO));
read_len = f_kf.read(reinterpret_cast<char*>(&kf), sizeof(KEYFRAME_INFO));
if(read_len != sizeof(KEYFRAME_INFO)) {
qDebug() << "invaid .tpk file.";
_notify_error(LOCAL8BIT("关键帧信息文件格式错误!"));
return false;
}
m_kf.push_back(kf);
}
return true;
}
void ThrData::_prepare() {
UpdateData* d = new UpdateData(TYPE_HEADER_INFO);
m_locker.lock();
m_data.enqueue(d);
m_locker.unlock();
}
UpdateData* ThrData::get_data() {
UpdateData* d = nullptr;
m_locker.lock();
if(m_data.size() > 0) {
// qDebug("get_data(), left: %d", m_data.size());
d = m_data.dequeue();
}
m_locker.unlock();
return d;
}
void ThrData::_clear_data() {
m_locker.lock();
while(m_data.size() > 0) {
UpdateData* d = m_data.dequeue();
delete d;
}
m_locker.unlock();
}