diff --git a/client/tp-player/bar.cpp b/client/tp-player/bar.cpp index ff034ff..61ebbcc 100644 --- a/client/tp-player/bar.cpp +++ b/client/tp-player/bar.cpp @@ -472,6 +472,22 @@ void Bar::onMousePress(int x, int y, Qt::MouseButton button) { m_owner->pause(); m_playing = false; m_owner->update(m_rc.left()+m_rc_btn_play.left(), m_rc.top()+m_rc_btn_play.top(), m_rc_btn_play.width(), m_rc_btn_play.height()); + + int percent = 0; + + if(pt.x() < m_rc_progress.left()) { + percent = 0; + m_resume_ms = 0; + } + else if(pt.x() > m_rc_progress.right()) { + percent = 100; + m_resume_ms = m_total_ms; + } + else { + percent = (pt.x() + m_img_progress_pointer[widget_normal].width()/2 - m_rc_progress.left()) * 100 / m_rc_progress.width(); + m_resume_ms = m_total_ms * percent / 100; + } + update_passed_time(m_resume_ms); } } diff --git a/client/tp-player/mainwindow.cpp b/client/tp-player/mainwindow.cpp index a914926..28d93d5 100644 --- a/client/tp-player/mainwindow.cpp +++ b/client/tp-player/mainwindow.cpp @@ -216,8 +216,8 @@ void MainWindow::resume(bool relocate, uint32_t ms) { } else if(m_play_state == PLAY_STATE_STOP) { // _start_play_thread(); - m_thr_data->restart(0); - m_thr_play->resume(true, 0); + m_thr_data->restart(ms); + m_thr_play->resume(true, ms); } m_play_state = PLAY_STATE_RUNNING; @@ -247,6 +247,11 @@ void MainWindow::_do_update_data(UpdateData* dat) { if(!dat->get_image(&img_update, x, y, w, h)) return; + static int img_idx = 0; + img_idx++; + qDebug("draw img: %d (%d,%d)-(%d,%d)", img_idx, x, y, w, h); + + QPainter pp(&m_canvas); pp.drawImage(x, y, *img_update, 0, 0, w, h, Qt::AutoColor); diff --git a/client/tp-player/record_format.h b/client/tp-player/record_format.h index 2f95c7b..3fe863c 100644 --- a/client/tp-player/record_format.h +++ b/client/tp-player/record_format.h @@ -93,6 +93,12 @@ typedef struct TS_RECORD_RDP_IMAGE_INFO { uint8_t _reserved; }TS_RECORD_RDP_IMAGE_INFO; +// 关键帧索引 +typedef struct TS_RECORD_RDP_KEYFRAME_INFO { + uint32_t time_ms; // 此关键帧的时间点 + uint32_t file_index; // 此关键帧图像数据位于哪一个数据文件中 + uint32_t offset; // 此关键帧图像数据在数据文件中的偏移 +}TS_RECORD_RDP_KEYFRAME_INFO; #pragma pack(pop) diff --git a/client/tp-player/rle.c b/client/tp-player/rle.c index 393e333..7aa0b49 100644 --- a/client/tp-player/rle.c +++ b/client/tp-player/rle.c @@ -76,7 +76,7 @@ RD_BOOL bitmap_decompress1(uint8 * output, int width, int height, const uint8 * input, int size) { - uint8 *end = input + size; + const uint8 *end = input + size; uint8 *prevline = NULL, *line = NULL; int opcode, count, offset, isfillormix, x = width; int lastopcode = -1, insertmix = False, bicolour = False; @@ -274,7 +274,7 @@ bitmap_decompress1(uint8 * output, int width, int height, const uint8 * input, i RD_BOOL bitmap_decompress2(uint8 * output, int width, int height, const uint8 * input, int size) { - uint8 *end = input + size; + const uint8 *end = input + size; uint16 *prevline = NULL, *line = NULL; int opcode, count, offset, isfillormix, x = width; int lastopcode = -1, insertmix = False, bicolour = False; diff --git a/client/tp-player/thr_data.cpp b/client/tp-player/thr_data.cpp index b029439..6c56156 100644 --- a/client/tp-player/thr_data.cpp +++ b/client/tp-player/thr_data.cpp @@ -13,6 +13,78 @@ #include "record_format.h" #include "mainwindow.h" +#include "rle.h" + +// for test only +int g_kf_idx = 0; +QByteArray g_kfdata[10]; +QByteArray* g_kf = nullptr; + +int g_img_idx = 0; + +void _update_key_frame(QByteArray* kf, uint16_t screen_w, uint16_t screen_h, uint16_t destLeft, uint16_t destTop, uint16_t w, uint16_t h, uint16_t wr, uint16_t hr, uint16_t bitsPerPixel, bool isCompressed, const uint8_t* dat, size_t len) { + switch(bitsPerPixel) { +// case 15: +// if(isCompressed) { +// uint8_t* _dat = reinterpret_cast(calloc(1, w*h*2)); +// if(!bitmap_decompress1(_dat, w, h, dat, len)) { +// free(_dat); +// qDebug("bitmap_decompress1() failed."); +// return; +// } +// out = new QImage(_dat, w, h, QImage::Format_RGB555); +// free(_dat); +// } +// else { +// out = new QImage(QImage(dat, w, h, QImage::Format_RGB555).transformed(QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))); +// } +// return out; + + case 16: + { + g_img_idx++; + uint8_t* kfd = reinterpret_cast(kf->data()); + if(isCompressed) { + uint8_t* _dat = reinterpret_cast(calloc(1, w*h*2)); + if(!bitmap_decompress2(_dat, w, h, dat, static_cast(len))) { + free(_dat); + qDebug() << "------------------DECOMPRESS2 failed."; + return; + } + +// out = new QImage(w, h, QImage::Format_RGB16); + + qDebug("c: %ld, img: %d (%d,%d)-(%d,%d) (%d,%d)", ((destTop+hr-1)*screen_w)+destLeft+wr-1, g_img_idx, destLeft, destTop, w, h, wr, hr); + for(int y = 0; y < hr; y++) { +// if((destTop+y)*screen_w+destLeft > 6) +// memcpy(kfd+((destTop+y)*screen_w+destLeft - 6)*2, _dat+((y*w)*2), wr*2); +// else + memcpy(kfd+((destTop+y)*screen_w+destLeft)*2, _dat+((y*w)*2), wr*2); + } + + free(_dat); + return; + } + else { +// out = new QImage(QImage(dat, w, h, QImage::Format_RGB16).transformed(QMatrix(1.0, 0.0, 0.0, -1.0, 0.0, 0.0))); + + qDebug("nc: %ld, img: %d (%d,%d)-(%d,%d) (%d,%d)", ((destTop+hr-1)*screen_w)+destLeft+wr-1, g_img_idx, destLeft, destTop, w, h, wr, hr); + for(int y = 0; y < hr; y++) { + memcpy(kfd+((destTop+h-y)*screen_w+destLeft)*2, dat+(y*w*2), wr*2); + } + } + + return; + } + + case 24: + case 32: + default: + qDebug() << "------------------NOT support UNKNOWN bitsPerPix" << bitsPerPixel; + return; + } +} + //================================================================= // ThrData @@ -30,6 +102,8 @@ ThrData::ThrData(MainWindow* mainwin, const QString& res) { m_file_idx = 0; m_offset = 0; + m_xxx = false; + #ifdef __APPLE__ m_data_path_base = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); m_data_path_base += "/tp-testdata/"; @@ -107,8 +181,10 @@ void ThrData::_run() { msleep(100); } - if(!m_thr_download.is_tpk_downloaded()) + if(!m_thr_download.is_tpk_downloaded()) { + _notify_error(QString("%1\n%2").arg(LOCAL8BIT("无法下载录像文件!"), m_res)); return; + } m_thr_download.get_data_path(m_data_path); } @@ -154,6 +230,11 @@ void ThrData::_run() { qint64 read_len = 0; QString str_fidx; + g_kf_idx = 0; + g_kf = &(g_kfdata[g_kf_idx]); + g_kf->resize(m_hdr.basic.width*m_hdr.basic.height*2); + memset(g_kf->data(), 0, m_hdr.basic.width*m_hdr.basic.height*2); + for(;;) { // 任何时候确保第一时间响应退出操作 if(m_need_stop) @@ -282,21 +363,80 @@ void ThrData::_run() { } file_processed += pkg.size; - UpdateData* dat = new UpdateData(); + + + + + // for test only + if(!m_xxx && pkg.type == TS_RECORD_TYPE_RDP_IMAGE) { + const TS_RECORD_RDP_IMAGE_INFO* info = reinterpret_cast(pkg_data.data()); + uint8_t* img_dat = reinterpret_cast(pkg_data.data() + sizeof(TS_RECORD_RDP_IMAGE_INFO)); + size_t img_len = pkg_data.size() - sizeof(TS_RECORD_RDP_IMAGE_INFO); + + bool isCompress = (info->format == TS_RDP_IMG_BMP) ? true : false; +// _update_key_frame(&g_kf, m_hdr.basic.width, m_hdr.basic.height, info->destLeft, info->destTop, (info->destRight-info->destLeft+1), (info->destBottom-info->destTop+1), info->bitsPerPixel, isCompress, img_dat, img_len); + _update_key_frame(g_kf, m_hdr.basic.width, m_hdr.basic.height, + info->destLeft, info->destTop, + info->width, info->height, + info->destRight - info->destLeft + 1, info->destBottom - info->destTop + 1, + info->bitsPerPixel, isCompress, img_dat, img_len); + } + if(pkg.type == TS_RECORD_TYPE_RDP_KEYFRAME) { +// const TS_RECORD_RDP_KEYFRAME_INFO* info = reinterpret_cast(pkg_data.data()); + uint8_t* img_dat = reinterpret_cast(pkg_data.data() + sizeof(TS_RECORD_RDP_KEYFRAME_INFO)); + uint32_t img_len = pkg_data.size() - sizeof(TS_RECORD_RDP_KEYFRAME_INFO); + + if(m_xxx) { + qDebug("use kf: %d", m_restart_kf_idx); + memcpy(img_dat, g_kfdata[m_restart_kf_idx].data(), img_len); + } + else { + memcpy(img_dat, g_kf->data(), img_len); + } + } + + + + + + UpdateData* dat = new UpdateData(m_hdr.basic.width, m_hdr.basic.height); if(!dat->parse(pkg, pkg_data)) { qDebug("invaid tp-rdp-%d.tpd file (4).", m_file_idx+1); _notify_error(QString("%1\ntp-rdp-%2.tpd").arg(LOCAL8BIT("错误的录像数据文件!"), str_fidx)); return; } + + + + // 拖动滚动条后,需要显示一次关键帧数据,然后跳过后续关键帧。 if(pkg.type == TS_RECORD_TYPE_RDP_KEYFRAME) { + g_kf_idx++; + g_kf = &(g_kfdata[g_kf_idx]); + if(!m_xxx) { + g_kf->resize(m_hdr.basic.width*m_hdr.basic.height*2); + memcpy(g_kf->data(), g_kfdata[g_kf_idx-1].data(), m_hdr.basic.width*m_hdr.basic.height*2); + } + qDebug("----key frame: %ld, processed=%" PRId64 ", pkg.size=%d", pkg.time_ms, file_processed, pkg.size); if(m_need_show_kf) { m_need_show_kf = false; qDebug("++ show keyframe."); } else { + //m_restart_kf_idx + + + QString tmp; + tmp.sprintf("%d", g_kf_idx); + QString img_fname = QString("%1/img-%2.png").arg(m_data_path, tmp); + QImage* img = nullptr; + int x = 0, y = 0, w = 0, h = 0; + dat->get_image(&img, x, y, w, h); + if(img != nullptr) + img->save(img_fname, "png"); + qDebug("-- skip keyframe."); delete dat; dat = nullptr; @@ -304,6 +444,9 @@ void ThrData::_run() { } + + + // 数据放到待播放列表中 if(dat) { m_locker.lock(); @@ -334,6 +477,7 @@ void ThrData::_run() { } void ThrData::restart(uint32_t start_ms) { + qDebug("restart at %ld ms", start_ms); // 让处理线程处理完当前循环,然后等待 m_need_restart = true; @@ -353,16 +497,26 @@ void ThrData::restart(uint32_t start_ms) { m_offset = 0; m_file_idx = 0; m_need_show_kf = false; + + g_kf_idx = 0; + m_restart_kf_idx = 0; + m_xxx = true; } else { // 找到最接近 start_ms 但小于它的关键帧 size_t i = 0; for(i = 0; i < m_kf.size(); ++i) { - if(m_kf[i].time_ms > start_ms) + if(m_kf[i].time_ms > start_ms) { break; + } } if(i > 0) i--; + g_kf_idx = i; + m_restart_kf_idx = i; + m_xxx = true; + + qDebug("restart acturelly at %ld ms, kf: %d", m_kf[i].time_ms, i); // 指定要播放的数据的开始位置 m_offset = m_kf[i].offset; diff --git a/client/tp-player/thr_data.h b/client/tp-player/thr_data.h index ee3861f..18c07c6 100644 --- a/client/tp-player/thr_data.h +++ b/client/tp-player/thr_data.h @@ -101,6 +101,9 @@ private: bool m_need_show_kf; uint32_t m_file_idx; uint32_t m_offset; + + bool m_xxx; + int m_restart_kf_idx; }; #endif // THR_DATA_H diff --git a/client/tp-player/update_data.cpp b/client/tp-player/update_data.cpp index 7faca7a..ac25d01 100644 --- a/client/tp-player/update_data.cpp +++ b/client/tp-player/update_data.cpp @@ -28,6 +28,7 @@ static QImage* _rdpimg2QImage(int w, int h, int bitsPerPixel, bool isCompressed, uint8_t* _dat = reinterpret_cast(calloc(1, w*h*2)); if(!bitmap_decompress2(_dat, w, h, dat, len)) { free(_dat); + qDebug() << "22------------------DECOMPRESS2 failed."; return nullptr; } @@ -58,6 +59,22 @@ static QImage* _rdpimg2QImage(int w, int h, int bitsPerPixel, bool isCompressed, } } +static QImage* _raw2QImage(int w, int h, const uint8_t* dat, uint32_t len) { + QImage* out; + + // TODO: 这里需要进一步优化,直接操作QImage的buffer。 + out = new QImage(w, h, QImage::Format_RGB16); + for(int y = 0; y < h; y++) { + for(int x = 0; x < w; x++) { + uint16 a = ((uint16*)dat)[y * w + x]; + uint8 r = ((a & 0xf800) >> 11) * 255 / 31; + uint8 g = ((a & 0x07e0) >> 5) * 255 / 63; + uint8 b = (a & 0x001f) * 255 / 31; + out->setPixelColor(x, y, QColor(r,g,b)); + } + } + return out; +} UpdateData::UpdateData() : QObject(nullptr) { @@ -78,6 +95,12 @@ UpdateData::UpdateData(const TS_RECORD_HEADER& hdr) : QObject(nullptr) memcpy(m_hdr, &hdr, sizeof(TS_RECORD_HEADER)); } +UpdateData::UpdateData(uint16_t screen_w, uint16_t screen_h) { + _init(); + m_screen_w = screen_w; + m_screen_h = screen_h; +} + void UpdateData::_init() { m_data_type = TYPE_UNKNOWN; m_hdr = nullptr; @@ -88,6 +111,9 @@ void UpdateData::_init() { m_data_buf = nullptr; m_data_len = 0; m_time_ms = 0; + + m_screen_w = 0; + m_screen_h = 0; } UpdateData::~UpdateData() { @@ -133,18 +159,22 @@ bool UpdateData::parse(const TS_RECORD_PKG& pkg, const QByteArray& data) { m_img_w = info->destRight - info->destLeft + 1; m_img_h = info->destBottom - info->destTop + 1; - - -// m_img_info = new TS_RECORD_RDP_IMAGE_INFO; -// memcpy(m_img_info, data.data(), sizeof(TS_RECORD_RDP_IMAGE_INFO)); -// m_data_buf = new uint8_t[img_len]; -// memcpy(m_data_buf, img_dat, img_len); -// m_data_len = img_len; - - return true; } else if(pkg.type == TS_RECORD_TYPE_RDP_KEYFRAME) { + m_data_type = TYPE_IMAGE; +// const TS_RECORD_RDP_KEYFRAME_INFO* info = reinterpret_cast(data.data()); + const uint8_t* img_dat = reinterpret_cast(data.data() + sizeof(TS_RECORD_RDP_KEYFRAME_INFO)); + uint32_t img_len = data.size() - sizeof(TS_RECORD_RDP_KEYFRAME_INFO); + + QImage* img = _raw2QImage((int)m_screen_w, (int)m_screen_h, img_dat, img_len); + if(img == nullptr) + return false; + m_img = img; + m_img_x = 0; + m_img_y = 0; + m_img_w = m_screen_w; + m_img_h = m_screen_h; return true; } diff --git a/client/tp-player/update_data.h b/client/tp-player/update_data.h index c2dea27..e037588 100644 --- a/client/tp-player/update_data.h +++ b/client/tp-player/update_data.h @@ -22,6 +22,7 @@ public: explicit UpdateData(); explicit UpdateData(int data_type); explicit UpdateData(const TS_RECORD_HEADER& hdr); + explicit UpdateData(uint16_t screen_w, uint16_t screen_h); virtual ~UpdateData(); bool parse(const TS_RECORD_PKG& pkg, const QByteArray& data); @@ -82,6 +83,9 @@ private: int m_img_h; // TS_RECORD_RDP_IMAGE_INFO* m_img_info; + + uint16_t m_screen_w; + uint16_t m_screen_h; }; class UpdateDataHelper {