diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..49c4e10 --- /dev/null +++ b/CMakeLists.txt @@ -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}) diff --git a/customslider.cpp b/customslider.cpp new file mode 100644 index 0000000..670be62 --- /dev/null +++ b/customslider.cpp @@ -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()); +} diff --git a/customslider.h b/customslider.h new file mode 100644 index 0000000..9952625 --- /dev/null +++ b/customslider.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +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; +}; diff --git a/displaywind.cpp b/displaywind.cpp new file mode 100644 index 0000000..5d44ce2 --- /dev/null +++ b/displaywind.cpp @@ -0,0 +1,215 @@ +#include "displaywind.h" +#include "ui_displaywind.h" +#include +#include +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(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; + } + } +} + diff --git a/displaywind.h b/displaywind.h new file mode 100644 index 0000000..438c813 --- /dev/null +++ b/displaywind.h @@ -0,0 +1,75 @@ +#ifndef DISPLAYWIND_H +#define DISPLAYWIND_H + +#include +#include +#include "ijkmediaplayer.h" +#include "imagescaler.h" +#include +#include +#include + +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 diff --git a/displaywind.ui b/displaywind.ui new file mode 100644 index 0000000..ecf5b00 --- /dev/null +++ b/displaywind.ui @@ -0,0 +1,20 @@ + + + DisplayWind + + + + 0 + 0 + 771 + 471 + + + + Form + + + + + + diff --git a/ff_fferror.h b/ff_fferror.h new file mode 100644 index 0000000..bcc6c7d --- /dev/null +++ b/ff_fferror.h @@ -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 diff --git a/ff_ffplay.cpp b/ff_ffplay.cpp new file mode 100644 index 0000000..b55432d --- /dev/null +++ b/ff_ffplay.cpp @@ -0,0 +1,1542 @@ +#include "ff_ffplay.h" +#include +#include +#include +#include "ffmsg.h" +#include "sonic.h" +#include "screenshot.h" + +#include "easylogging++.h" +//#define LOG(INFO) std::cout +//#define LOG(ERROR) std::cout + +/* Minimum SDL audio buffer size, in samples. */ +#define SDL_AUDIO_MIN_BUFFER_SIZE 512 +/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */ +#define SDL_AUDIO_MAX_CALLBACKS_PER_SEC 30 +int infinite_buffer = 0; +static int decoder_reorder_pts = -1; +static int seek_by_bytes = -1; +void print_error(const char *filename, int err) +{ + char errbuf[128]; + const char *errbuf_ptr = errbuf; + if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) { + errbuf_ptr = strerror(AVUNERROR(err)); + } + av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, errbuf_ptr); +} + +FFPlayer::FFPlayer() +{ + pf_playback_rate = 1.0; + // 初始化统计信息 + ffp_reset_statistic(&stat); +} + +int FFPlayer::ffp_create() +{ + LOG(INFO) << "ffp_create\n"; + msg_queue_init(&msg_queue_); + return 0; +} + +void FFPlayer::ffp_destroy() +{ + stream_close(); + // 销毁消息队列 + msg_queue_destroy(&msg_queue_); +} + +int FFPlayer::ffp_prepare_async_l(char *file_name) +{ + //保存文件名 + input_filename_ = strdup(file_name); + int reval = stream_open(file_name); + return reval; +} + +// 开启播放 或者恢复播放 +int FFPlayer::ffp_start_l() +{ + // 触发播放 + LOG(INFO) << "ffp_start_l"; + toggle_pause( 0); + return 0; +} + +int FFPlayer::ffp_stop_l() +{ + abort_request = 1; // 请求退出 + msg_queue_abort(&msg_queue_); // 禁止再插入消息 + return 0; +} + +int FFPlayer::stream_open(const char *file_name) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { + av_log(NULL, AV_LOG_FATAL, "Could not initialize SDL - %s\n", SDL_GetError()); + av_log(NULL, AV_LOG_FATAL, "(Did you set the DISPLAY variable?)\n"); + return -1; + } + // 初始化Frame帧队列 + if (frame_queue_init(&pictq, &videoq, VIDEO_PICTURE_QUEUE_SIZE_DEFAULT, 1) < 0) { + goto fail; + } + // 要注意最后一个值设置为1的重要性 + if (frame_queue_init(&sampq, &audioq, SAMPLE_QUEUE_SIZE, 1) < 0) { + goto fail; + } + // 初始化Packet包队列 + if (packet_queue_init(&videoq) < 0 || + packet_queue_init(&audioq) < 0 ) { + goto fail; + } + // 初始化时钟 + /* + * 初始化时钟 + * 时钟序列->queue_serial,实际上指向的是videoq.serial + */ + init_clock(&vidclk, &videoq.serial); + init_clock(&audclk, &audioq.serial); + audio_clock_serial = -1; + // 初始化音量等 + startup_volume = av_clip(startup_volume, 0, 100); + startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME); + audio_volume = startup_volume; + // 创建解复用器读数据线程read_thread + read_thread_ = new std::thread(&FFPlayer::read_thread, this); + // 创建视频刷新线程 + video_refresh_thread_ = new std::thread(&FFPlayer::video_refresh_thread, this); + return 0; +fail: + stream_close(); + return -1; +} + +void FFPlayer::stream_close() +{ + abort_request = 1; // 请求退出 + if(read_thread_ && read_thread_->joinable()) { + read_thread_->join(); // 等待线程退出 + } + /* close each stream */ + if (audio_stream >= 0) { + stream_component_close(audio_stream); // 解码器线程请求abort的时候有调用 packet_queue_abort + } + if (video_stream >= 0) { + stream_component_close(video_stream); + } + // 关闭解复用器 avformat_close_input(&ic); + // 释放packet队列 + packet_queue_destroy(&videoq); + packet_queue_destroy(&audioq); + // 释放frame队列 + frame_queue_destory(&pictq); + frame_queue_destory(&sampq); + if(input_filename_) { + free(input_filename_); + input_filename_ = NULL; + } +} + +// 如果想指定解码器怎么处理? +int FFPlayer::stream_component_open(int stream_index) +{ + AVCodecContext *avctx; + AVCodec *codec; + int sample_rate; + int nb_channels; + int64_t channel_layout; + int ret = 0; + // 判断stream_index是否合法 + if (stream_index < 0 || stream_index >= ic->nb_streams) { + return -1; + } + /* 为解码器分配一个编解码器上下文结构体 */ + avctx = avcodec_alloc_context3(NULL); + if (!avctx) { + return AVERROR(ENOMEM); + } + /* 将码流中的编解码器信息拷贝到新分配的编解码器上下文结构体 */ + ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar); + if (ret < 0) { + goto fail; + } + // 设置pkt_timebase + avctx->pkt_timebase = ic->streams[stream_index]->time_base; + /* 根据codec_id查找解码器 */ + codec = (AVCodec *)avcodec_find_decoder(avctx->codec_id); + if (!codec) { + av_log(NULL, AV_LOG_WARNING, + "No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id)); + ret = AVERROR(EINVAL); + goto fail; + } + if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) { + goto fail; + } + switch (avctx->codec_type) { + case AVMEDIA_TYPE_AUDIO: + //从avctx(即AVCodecContext)中获取音频格式参数 + sample_rate = avctx->sample_rate;; // 采样率 + nb_channels = avctx->channels;; // 通道数 + channel_layout = avctx->channel_layout;; // 通道布局 + /* prepare audio output 准备音频输出*/ + //调用audio_open打开sdl音频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区大小 + if ((ret = audio_open( channel_layout, nb_channels, sample_rate, &audio_tgt)) < 0) { + goto fail; + } + audio_hw_buf_size = ret; + audio_src = audio_tgt; //暂且将数据源参数等同于目标输出参数 + //初始化audio_buf相关参数 + audio_buf_size = 0; + audio_buf_index = 0; + audio_stream = stream_index; // 获取audio的stream索引 + audio_st = ic->streams[stream_index]; // 获取audio的stream指针 + // 初始化ffplay封装的音频解码器, 并将解码器上下文 avctx和Decoder绑定 + auddec.decoder_init(avctx, &audioq); + // 启动音频解码线程 + auddec.decoder_start(AVMEDIA_TYPE_AUDIO, "audio_thread", this); + // 允许音频输出 + //play audio + SDL_PauseAudio(0); + break; + case AVMEDIA_TYPE_VIDEO: + video_stream = stream_index; // 获取video的stream索引 + video_st = ic->streams[stream_index];// 获取video的stream指针 + // // 初始化ffplay封装的视频解码器 + viddec.decoder_init(avctx, &videoq); // + // // 启动视频频解码线程 + if ((ret = viddec.decoder_start(AVMEDIA_TYPE_VIDEO, "video_decoder", this)) < 0) { + goto out; + } + break; + default: + break; + } + goto out; +fail: + avcodec_free_context(&avctx); +out: + return ret; +} + +void FFPlayer::stream_component_close(int stream_index) +{ + AVCodecParameters *codecpar; + if (stream_index < 0 || stream_index >= ic->nb_streams) { + return; + } + codecpar = ic->streams[stream_index]->codecpar; + switch (codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + LOG(INFO) << " AVMEDIA_TYPE_AUDIO\n"; + // 请求终止解码器线程 + auddec.decoder_abort(&sampq); + // 关闭音频设备 + audio_close(); + // 销毁解码器 + auddec.decoder_destroy(); + // 释放重采样器 + swr_free(&swr_ctx); + // 释放audio buf + av_freep(&audio_buf1); + audio_buf1_size = 0; + audio_buf = NULL; + break; + case AVMEDIA_TYPE_VIDEO: + // 请求退出视频画面刷新线程 + if(video_refresh_thread_ && video_refresh_thread_->joinable()) { + video_refresh_thread_->join(); // 等待线程退出 + } + LOG(INFO) << " AVMEDIA_TYPE_VIDEO\n"; + // 请求终止解码器线程 + // 关闭音频设备 + // 销毁解码器 + viddec.decoder_abort(&pictq); + viddec.decoder_destroy(); + break; + default: + break; + } + // ic->streams[stream_index]->discard = AVDISCARD_ALL; // 这个又有什么用? + switch (codecpar->codec_type) { + case AVMEDIA_TYPE_AUDIO: + audio_st = NULL; + audio_stream = -1; + break; + case AVMEDIA_TYPE_VIDEO: + video_st = NULL; + video_stream = -1; + break; + default: + break; + } +} + +/** + * Decode one audio frame and return its uncompressed size. + * + * The processed audio frame is decoded, converted if required, and + * stored in audio_buf, with size in bytes given by the return + * value. + */ +static int audio_decode_frame(FFPlayer *is) +{ + int data_size, resampled_data_size; + int64_t dec_channel_layout; + int wanted_nb_samples; + Frame *af; + int ret = 0; + if(is->paused) { + return -1; + } + // 读取一帧数据 + do { + // 若队列头部可读,则由af指向可读帧 + if (!(af = frame_queue_peek_readable(&is->sampq))) { + return -1; + } + frame_queue_next(&is->sampq); // 不同序列的出队列 + } while (af->serial != is->audioq.serial); // 这里容易出现af->serial != audioq.serial 一直循环 + // 根据frame中指定的音频参数获取缓冲区的大小 af->frame->channels * af->frame->nb_samples * 2 + data_size = av_samples_get_buffer_size(NULL, av_frame_get_channels(af->frame), + af->frame->nb_samples, + (enum AVSampleFormat)af->frame->format, 1); + // 获取声道布局 + dec_channel_layout = (af->frame->channel_layout && af->frame->channels == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ? + af->frame->channel_layout : av_get_default_channel_layout(av_frame_get_channels(af->frame)); + if(dec_channel_layout == 0) { + LOG(INFO) << af->frame->channel_layout << ", failed: " << av_get_default_channel_layout(af->frame->channels) ; + dec_channel_layout = 3; // fixme + return -1; // 这个是异常情况 + } + // 获取样本数校正值:若同步时钟是音频,则不调整样本数;否则根据同步需要调整样本数 + // wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples); // 目前不考虑非音视频同步的是情况 + wanted_nb_samples = af->frame->nb_samples; + // audio_tgt是SDL可接受的音频帧数,是audio_open()中取得的参数 + // 在audio_open()函数中又有"audio_src = audio_tgt"" + // 此处表示:如果frame中的音频参数 == audio_src == audio_tgt, + // 那音频重采样的过程就免了(因此时swr_ctr是NULL) + // 否则使用frame(源)和audio_tgt(目标)中的音频参数来设置swr_ctx, + // 并使用frame中的音频参数来赋值audio_src + if (af->frame->format != is->audio_src.fmt || // 采样格式 + dec_channel_layout != is->audio_src.channel_layout || // 通道布局 + af->frame->sample_rate != is->audio_src.freq || // 采样率 + (wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx) ) { + swr_free(&is->swr_ctx); + is->swr_ctx = swr_alloc_set_opts(NULL, + is->audio_tgt.channel_layout, // 目标输出 + is->audio_tgt.fmt, + is->audio_tgt.freq, + dec_channel_layout, // 数据源 + (enum AVSampleFormat)af->frame->format, + af->frame->sample_rate, + 0, NULL); + int ret = 0; + if (!is->swr_ctx || (ret = swr_init(is->swr_ctx)) < 0) { + char errstr[256] = { 0 }; + av_strerror(ret, errstr, sizeof(errstr)); + LOG(INFO) << "swr_init failed:" << errstr ; + sprintf(errstr, "Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n", + af->frame->sample_rate, av_get_sample_fmt_name((enum AVSampleFormat)af->frame->format), af->frame->channels, + is-> audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels); + LOG(INFO) << errstr; + swr_free(&is->swr_ctx); + ret = -1; + goto fail; + } + is->audio_src.channel_layout = dec_channel_layout; + is->audio_src.channels = af->frame->channels; + is->audio_src.freq = af->frame->sample_rate; + is->audio_src.fmt = (enum AVSampleFormat)af->frame->format; + } + if (is->swr_ctx) { + // 重采样输入参数1:输入音频样本数是af->frame->nb_samples + // 重采样输入参数2:输入音频缓冲区 + const uint8_t **in = (const uint8_t **)af->frame->extended_data; // data[0] data[1] + // 重采样输出参数1:输出音频缓冲区 + uint8_t **out = &is->audio_buf1; //真正分配缓存audio_buf1,指向是用audio_buf + // 重采样输出参数2:输出音频缓冲区尺寸, 高采样率往低采样率转换时得到更少的样本数量,比如 96k->48k, wanted_nb_samples=1024 + // 则wanted_nb_samples * audio_tgt.freq / af->frame->sample_rate 为1024*48000/96000 = 512 + // +256 的目的是重采样内部是有一定的缓存,就存在上一次的重采样还缓存数据和这一次重采样一起输出的情况,所以目的是多分配输出buffer + int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + + 256; + // 计算对应的样本数 对应的采样格式 以及通道数,需要多少buffer空间 + int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels, + out_count, is->audio_tgt.fmt, 0); + int len2; + if (out_size < 0) { + av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n"); + ret = -1; + goto fail; + } + // if(audio_buf1_size < out_size) {重新分配out_size大小的缓存给audio_buf1, 并将audio_buf1_size设置为out_size } + av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size); + if (!is->audio_buf1) { + ret = AVERROR(ENOMEM); + goto fail; + } + // 音频重采样:len2返回值是重采样后得到的音频数据中单个声道的样本数 + len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples); + if (len2 < 0) { + av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n"); + ret = -1; + goto fail; + } + if (len2 == out_count) { // 这里的意思是我已经多分配了buffer,实际输出的样本数不应该超过我多分配的数量 + av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n"); + if (swr_init(is->swr_ctx) < 0) { + swr_free(&is->swr_ctx); + } + } + // 重采样返回的一帧音频数据大小(以字节为单位) + is->audio_buf = is->audio_buf1; + resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt); + } else { + // 未经重采样,则将指针指向frame中的音频数据 + is->audio_buf = af->frame->data[0]; // s16交错模式data[0], fltp data[0] data[1] + resampled_data_size = data_size; + } + if (!std::isnan(af->pts)) { + is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate; + } else { + is->audio_clock = NAN; + } + is->audio_clock_serial = af->serial; // 保存当前解码帧的serial + ret = resampled_data_size; +fail: + return ret; +} + + + +/* prepare a new audio buffer */ +/** + * @brief sdl_audio_callback + * @param opaque 指向user的数据 + * @param stream 拷贝PCM的地址 + * @param len 需要拷贝的长度 + */ +static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) +{ + // 2ch 2字节 1024 = 4096 -> 回调每次读取2帧数据 + FFPlayer *is = (FFPlayer *)opaque; + int audio_size, len1; + is->audio_callback_time = av_gettime_relative(); + while (len > 0) { // 循环读取,直到读取到足够的数据 + /* (1)如果audio_buf_index < audio_buf_size则说明上次拷贝还剩余一些数据, + * 先拷贝到stream再调用audio_decode_frame + * (2)如果audio_buf消耗完了,则调用audio_decode_frame重新填充audio_buf + */ + if (is->audio_buf_index >= is->audio_buf_size) { + audio_size = audio_decode_frame(is); + // LOG(INFO) << " audio_size: " << audio_size; + if (audio_size < 0) { + /* if error, just output silence */ + is->audio_buf = NULL; + is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size + * is->audio_tgt.frame_size; + is->audio_no_data = 1; // 没有数据可以读取 + if(is->eof) { + // 如果文件以及读取完毕,此时应该判断是否还有数据可以读取,如果没有就该发送通知ui停止播放 + is->check_play_finish(); + } + } else { + is->audio_buf_size = audio_size; // 讲字节 多少字节 + is->audio_no_data = 0; + } + is->audio_buf_index = 0; + // 2 是否需要做变速 + if(is->ffp_get_playback_rate_change()) { + is->ffp_set_playback_rate_change(0); + // 初始化 + if(is->audio_speed_convert) { + // 先释放 + sonicDestroyStream(is->audio_speed_convert); + } + // 再创建 + is->audio_speed_convert = sonicCreateStream(is->get_target_frequency(), + is->get_target_channels()); + // 设置变速系数 + sonicSetSpeed(is->audio_speed_convert, is->ffp_get_playback_rate()); + sonicSetPitch(is->audio_speed_convert, 1.0); + sonicSetRate(is->audio_speed_convert, 1.0); + } + if(!is->is_normal_playback_rate() && is->audio_buf) { + // 不是正常播放则需要修改 + // 需要修改 audio_buf_index audio_buf_size audio_buf + int actual_out_samples = is->audio_buf_size / + (is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt)); + // 计算处理后的点数 + int out_ret = 0; + int out_size = 0; + int num_samples = 0; + int sonic_samples = 0; + if(is->audio_tgt.fmt == AV_SAMPLE_FMT_FLT) { + out_ret = sonicWriteFloatToStream(is->audio_speed_convert, + (float *)is->audio_buf, + actual_out_samples); + } else if(is->audio_tgt.fmt == AV_SAMPLE_FMT_S16) { + out_ret = sonicWriteShortToStream(is->audio_speed_convert, + (short *)is->audio_buf, + actual_out_samples); + } else { + av_log(NULL, AV_LOG_ERROR, "sonic unspport ......\n"); + } + num_samples = sonicSamplesAvailable(is->audio_speed_convert); + // 2通道 目前只支持2通道的 + out_size = (num_samples) * av_get_bytes_per_sample(is->audio_tgt.fmt) * is->audio_tgt.channels; + av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size); + if(out_ret) { + // 从流中读取处理好的数据 + if(is->audio_tgt.fmt == AV_SAMPLE_FMT_FLT) { + sonic_samples = sonicReadFloatFromStream(is->audio_speed_convert, + (float *)is->audio_buf1, + num_samples); + } else if(is->audio_tgt.fmt == AV_SAMPLE_FMT_S16) { + sonic_samples = sonicReadShortFromStream(is->audio_speed_convert, + (short *)is->audio_buf1, + num_samples); + } else { + LOG(ERROR) << "sonic unspport fmt: " << is->audio_tgt.fmt; + } + is->audio_buf = is->audio_buf1; + // LOG(INFO) << "mdy num_samples: " << num_samples; + // LOG(INFO) << "orig audio_buf_size: " << audio_buf_size; + is->audio_buf_size = sonic_samples * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt); + // LOG(INFO) << "mdy audio_buf_size: " << audio_buf_size; + is->audio_buf_index = 0; + } + } + } + if(is->audio_buf_size == 0) { + continue; + } + //根据缓冲区剩余大小量力而行 + len1 = is->audio_buf_size - is->audio_buf_index; + if (len1 > len) { + len1 = len; + } + if (is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME) { + memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1); + } else { + memset(stream, 0, len1); + if (is->audio_buf) { + SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume); + } + } + /* 更新audio_buf_index,指向audio_buf中未被拷贝到stream的数据(剩余数据)的起始位置 */ + len -= len1; + stream += len1; + is->audio_buf_index += len1; + } + is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index; + /* Let's assume the audio driver that is used by SDL has two periods. */ + if (!std::isnan(is->audio_clock)) { + double audio_clock = is->audio_clock / is->ffp_get_playback_rate(); + set_clock_at(&is->audclk, + audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, + is->audio_clock_serial, + is->audio_callback_time / 1000000.0); + } +} + + +// 先参考我们之前讲的06-sdl-pcm范例 +int FFPlayer::audio_open(int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, AudioParams *audio_hw_params) +{ + SDL_AudioSpec wanted_spec; + // 音频参数设置SDL_AudioSpec + wanted_spec.freq = wanted_sample_rate; // 采样频率 + wanted_spec.format = AUDIO_S16SYS; // 采样点格式 + wanted_spec.channels = wanted_nb_channels; // 2通道 + wanted_spec.silence = 0; + wanted_spec.samples = 2048; // 23.2ms -> 46.4ms 每次读取的采样数量,多久产生一次回调和 samples + wanted_spec.callback = sdl_audio_callback; // 回调函数 + wanted_spec.userdata = this; + // SDL_OpenAudioDevice + //打开音频设备 + if(SDL_OpenAudio(&wanted_spec, NULL) != 0) { + LOG(ERROR) << "Failed to open audio device, err: " << SDL_GetError(); + return -1; + } + // wanted_spec是期望的参数,spec是实际的参数,wanted_spec和spec都是SDL中的结构。 + // 此处audio_hw_params是FFmpeg中的参数,输出参数供上级函数使用 + // audio_hw_params保存的参数,就是在做重采样的时候要转成的格式。 + audio_hw_params->fmt = AV_SAMPLE_FMT_S16; + audio_hw_params->freq = wanted_spec.freq; + audio_hw_params->channel_layout = wanted_channel_layout; + audio_hw_params->channels = wanted_spec.channels; + if(audio_hw_params->channel_layout == 0) { + audio_hw_params->channel_layout = + av_get_default_channel_layout(audio_hw_params->channels); + LOG(WARNING) << "layout is 0, force change to " << audio_hw_params->channel_layout; + } + /* audio_hw_params->frame_size这里只是计算一个采样点占用的字节数 */ + audio_hw_params->frame_size = av_samples_get_buffer_size(NULL, audio_hw_params->channels, + 1, + audio_hw_params->fmt, 1); + audio_hw_params->bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params->channels, + audio_hw_params->freq, + audio_hw_params->fmt, 1); + if (audio_hw_params->bytes_per_sec <= 0 || audio_hw_params->frame_size <= 0) { + av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size failed\n"); + return -1; + } + // 比如2帧数据,一帧就是1024个采样点, 1024*2*2 * 2 = 8192字节 + return wanted_spec.size; /* SDL内部缓存的数据字节, samples * channels *byte_per_sample */ +} + +void FFPlayer::audio_close() +{ + SDL_CloseAudio(); // SDL_CloseAudioDevice +} + +long FFPlayer::ffp_get_duration_l() +{ + if(!ic) { + return 0; + } + int64_t duration = fftime_to_milliseconds(ic->duration); + if (duration < 0) { + return 0; + } + return (long)duration; +} + +// 当前播放的位置 +long FFPlayer::ffp_get_current_position_l() +{ + if(!ic) { + return 0; + } + int64_t start_time = ic->start_time; // 起始时间 一般为0 + int64_t start_diff = 0; + if (start_time > 0 && start_time != AV_NOPTS_VALUE) { + start_diff = fftime_to_milliseconds(start_time); // 返回只需ms这个级别的 + } + int64_t pos = 0; + double pos_clock = get_master_clock(); // 获取当前时钟 + if (std::isnan(pos_clock)) { + pos = fftime_to_milliseconds(seek_pos); + } else { + pos = pos_clock * 1000; //转成msg + } + if (pos < 0 || pos < start_diff) { + return 0; + } + int64_t adjust_pos = pos - start_diff; + return (long)adjust_pos * pf_playback_rate; // 变速的系数 +} + +// 暂停的请求 +int FFPlayer::ffp_pause_l() +{ + toggle_pause(1); + return 0; +} + +void FFPlayer::toggle_pause(int pause_on) +{ + toggle_pause_l(pause_on); +} + +void FFPlayer::toggle_pause_l(int pause_on) +{ + if (pause_req && !pause_on) { + set_clock(&vidclk, get_clock(&vidclk), vidclk.serial); + set_clock(&audclk, get_clock(&audclk), audclk.serial); + } + pause_req = pause_on; + auto_resume = !pause_on; + stream_update_pause_l(); + step = 0; +} + +void FFPlayer::stream_update_pause_l() +{ + if (!step && (pause_req || buffering_on)) { + stream_toggle_pause_l(1); + } else { + stream_toggle_pause_l(0); + } +} + +void FFPlayer::stream_toggle_pause_l(int pause_on) +{ + if (paused && !pause_on) { + frame_timer += av_gettime_relative() / 1000000.0 - vidclk.last_updated; + set_clock(&vidclk, get_clock(&vidclk), vidclk.serial); + set_clock(&audclk, get_clock(&audclk), audclk.serial); + } else { + } + if (step && (pause_req || buffering_on)) { + paused = vidclk.paused = pause_on; + } else { + paused = audclk.paused = vidclk.paused = pause_on; + // SDL_AoutPauseAudio(ffp->aout, pause_on); + } +} + +int FFPlayer::ffp_seek_to_l(long msec) +{ + int64_t start_time = 0; + int64_t seek_pos = milliseconds_to_fftime(msec); + int64_t duration = milliseconds_to_fftime(ffp_get_duration_l()); + if (duration > 0 && seek_pos >= duration) { + ffp_notify_msg1(this, FFP_MSG_SEEK_COMPLETE); // 超出了范围 + return 0; + } + start_time = ic->start_time; + if (start_time > 0 && start_time != AV_NOPTS_VALUE) { + seek_pos += start_time; + } + LOG(INFO) << "seek to: " << seek_pos / 1000 ; + stream_seek(seek_pos, 0, 0); + return 0; +} + +int FFPlayer::ffp_forward_to_l(long incr) +{ + ffp_forward_or_back_to_l(incr); + return 0; +} + +int FFPlayer::ffp_back_to_l(long incr) +{ + ffp_forward_or_back_to_l(incr); + return 0; +} + +int FFPlayer::ffp_forward_or_back_to_l(long incr) +{ + double pos; + if (seek_by_bytes) { + pos = -1; + if (pos < 0 && video_stream >= 0) { + pos = frame_queue_last_pos(&pictq); + } + if (pos < 0 && audio_stream >= 0) { + pos = frame_queue_last_pos(&sampq); + } + if (pos < 0) { + pos = avio_tell(ic->pb); + } + if (ic->bit_rate) { + incr *= ic->bit_rate / 8.0; + } else { + incr *= 180000.0; + } + pos += incr; + stream_seek(pos, incr, 1); + } else { + pos = get_master_clock(); // 单位是秒 + if (std::isnan(pos)) { + pos = (double)seek_pos / AV_TIME_BASE; + } + pos += incr; // 单位转成秒 + if (ic->start_time != AV_NOPTS_VALUE && pos < ic->start_time / (double)AV_TIME_BASE) { + pos = ic->start_time / (double)AV_TIME_BASE; + } + //转成 AV_TIME_BASE + stream_seek((int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0); + } + return 0; +} + +void FFPlayer::stream_seek(int64_t pos, int64_t rel, int seek_by_bytes) +{ + if (!seek_req) { + seek_pos = pos; + seek_rel = rel; + seek_flags &= ~AVSEEK_FLAG_BYTE; + if (seek_by_bytes) { + seek_flags |= AVSEEK_FLAG_BYTE; + } + seek_req = 1; + // SDL_CondSignal( continue_read_thread); + } +} + +int FFPlayer::ffp_screenshot_l(char *screen_path) +{ + // 存在视频的情况下才能截屏 + if(video_st && !req_screenshot_) { + if(screen_path_) { + free(screen_path_); + screen_path_ = NULL; + } + screen_path_ = strdup(screen_path); + req_screenshot_ = true; + } + return 0; +} + +void FFPlayer::screenshot(AVFrame *frame) +{ + if(req_screenshot_) { + ScreenShot shot; + int ret = -1; + if(frame) { + ret = shot.SaveJpeg(frame, screen_path_, 70); + } + // 如果正常则ret = 0; 异常则为 < 0 + ffp_notify_msg4(this, FFP_MSG_SCREENSHOT_COMPLETE, ret, 0, screen_path_, strlen(screen_path_) + 1); + // 截屏完毕后允许再次截屏 + req_screenshot_ = false; + } +} + +int FFPlayer::get_target_frequency() +{ + return audio_tgt.freq; +} + +int FFPlayer::get_target_channels() +{ + return audio_tgt.channels; +} + +void FFPlayer::ffp_set_playback_rate(float rate) +{ + pf_playback_rate = rate; + pf_playback_rate_changed = 1; +} + +float FFPlayer::ffp_get_playback_rate() +{ + return pf_playback_rate; +} + +bool FFPlayer::is_normal_playback_rate() +{ + if(pf_playback_rate > 0.99 && pf_playback_rate < 1.01) { + return true; + } else { + return false; + } +} + +int FFPlayer::ffp_get_playback_rate_change() +{ + return pf_playback_rate_changed; +} + +void FFPlayer::ffp_set_playback_rate_change(int change) +{ + pf_playback_rate_changed = change; +} + +void FFPlayer::ffp_set_playback_volume(int value) +{ + value = av_clip(value, 0, 100); + value = av_clip(SDL_MIX_MAXVOLUME * value / 100, 0, SDL_MIX_MAXVOLUME); + audio_volume = value; + LOG(INFO) << "audio_volume: " << audio_volume ; +} + +void FFPlayer::check_play_finish() +{ + // LOG(INFO) << "eof: " << eof << ", audio_no_data: " << audio_no_data ; + if(eof == 1) { // 1. av_read_frame已经返回了AVERROR_EOF + if(audio_stream >= 0 && video_stream >= 0) { // 2.1 音频、视频同时存在的场景 + if(audio_no_data == 1 && video_no_data == 1) { + // 发送停止 + ffp_notify_msg1(this, FFP_MSG_PLAY_FNISH); + } + return; + } + if(audio_stream >= 0) { // 2.2 只有音频存在 + if(audio_no_data == 1) { + // 发送停止 + ffp_notify_msg1(this, FFP_MSG_PLAY_FNISH); + } + return; + } + if(video_stream >= 0) { // 2.3 只有视频存在 + if(video_no_data == 1) { + // 发送停止 + ffp_notify_msg1(this, FFP_MSG_PLAY_FNISH); + } + return; + } + } +} +int64_t FFPlayer::ffp_get_property_int64(int id, int64_t default_value) +{ + switch (id) { + case FFP_PROP_INT64_AUDIO_CACHED_DURATION: + return stat.audio_cache.duration; + case FFP_PROP_INT64_VIDEO_CACHED_DURATION: + return stat.video_cache.duration; + default: + return default_value; + } +} +void FFPlayer::ffp_track_statistic_l(AVStream * st, PacketQueue * q, FFTrackCacheStatistic * cache) +{ + if (q) { + cache->bytes = q->size; + cache->packets = q->nb_packets; + } + if (q && st && st->time_base.den > 0 && st->time_base.num > 0) { + cache->duration = q->duration * av_q2d(st->time_base) * 1000; // 单位毫秒ms + } +} +// 在audio_thread解码线程做统计 +void FFPlayer::ffp_audio_statistic_l() +{ + ffp_track_statistic_l(audio_st, &audioq, &stat.audio_cache); +} +// 在audio_thread解码线程做统计 +void FFPlayer::ffp_video_statistic_l() +{ + ffp_track_statistic_l(video_st, &videoq, &stat.video_cache); +} +int FFPlayer::stream_has_enough_packets(AVStream * st, int stream_id, PacketQueue * queue) +{ + return stream_id < 0 || + queue->abort_request || + (st->disposition & AV_DISPOSITION_ATTACHED_PIC) || + queue->nb_packets > MIN_FRAMES && (!queue->duration || av_q2d(st->time_base) * queue->duration > 1.0); +} +static int is_realtime(AVFormatContext * s) +{ + if( !strcmp(s->iformat->name, "rtp") + || !strcmp(s->iformat->name, "rtsp") + || !strcmp(s->iformat->name, "sdp") + || !strcmp(s->iformat->name, "rtmp") + ) { + return 1; + } + if(s->pb && ( !strncmp(s->filename, "rtp:", 4) + || !strncmp(s->filename, "udp:", 4) + ) + ) { + return 1; + } + return 0; +} +int FFPlayer::read_thread() +{ + int err, ret; + int st_index[AVMEDIA_TYPE_NB]; // AVMEDIA_TYPE_VIDEO/ AVMEDIA_TYPE_AUDIO 等,用来保存stream index + AVPacket pkt1; + AVPacket *pkt = &pkt1; // + // 初始化为-1,如果一直为-1说明没相应steam + memset(st_index, -1, sizeof(st_index)); + video_stream = -1; + audio_stream = -1; + eof = 0; + // 1. 创建上下文结构体,这个结构体是最上层的结构体,表示输入上下文 + ic = avformat_alloc_context(); + if (!ic) { + av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n"); + ret = AVERROR(ENOMEM); + goto fail; + } + /* 3.打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等 */ + err = avformat_open_input(&ic, input_filename_, NULL, NULL); + if (err < 0) { + print_error(input_filename_, err); + ret = -1; + goto fail; + } + ffp_notify_msg1(this, FFP_MSG_OPEN_INPUT); + LOG(INFO) << "read_thread FFP_MSG_OPEN_INPUT " << this ; + if (seek_by_bytes < 0) { + seek_by_bytes = !!(ic->iformat->flags & AVFMT_TS_DISCONT) && strcmp("ogg", ic->iformat->name); + } + /* + * 4.探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息 + * 调用该函数后得多的参数信息会比只调用avformat_open_input更为详细, + * 其本质上是去做了decdoe packet获取信息的工作 + * codecpar, filled by libavformat on stream creation or + * in avformat_find_stream_info() + */ + err = avformat_find_stream_info(ic, NULL); + if (err < 0) { + av_log(NULL, AV_LOG_WARNING, + "%s: could not find codec parameters\n", input_filename_); + ret = -1; + goto fail; + } + ffp_notify_msg1(this, FFP_MSG_FIND_STREAM_INFO); + LOG(INFO) << "read_thread FFP_MSG_FIND_STREAM_INFO " << this ; + realtime = is_realtime(ic); + av_dump_format(ic, 0, input_filename_, 0); + // 6.2 利用av_find_best_stream选择流, + st_index[AVMEDIA_TYPE_VIDEO] = + av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO, + st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0); + st_index[AVMEDIA_TYPE_AUDIO] = + av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO, + st_index[AVMEDIA_TYPE_AUDIO], + st_index[AVMEDIA_TYPE_VIDEO], + NULL, 0); + /* open the streams */ + /* 8. 打开视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程。 */ + if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {// 如果有音频流则打开音频流 + stream_component_open(st_index[AVMEDIA_TYPE_AUDIO]); + } + ret = -1; + if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { // 如果有视频流则打开视频流 + ret = stream_component_open( st_index[AVMEDIA_TYPE_VIDEO]); + } + ffp_notify_msg1(this, FFP_MSG_COMPONENT_OPEN); + LOG(INFO) << "read_thread FFP_MSG_COMPONENT_OPEN " << this ; + if (video_stream < 0 && audio_stream < 0) { + av_log(NULL, AV_LOG_FATAL, "Failed to open file '%s' or configure filtergraph\n", + input_filename_); + ret = -1; + goto fail; + } + ffp_notify_msg1(this, FFP_MSG_PREPARED); + LOG(INFO) << "read_thread FFP_MSG_PREPARED " << this ; + while (1) { + // LOG(INFO) << "read_thread sleep, mp:" << this ; + // 先模拟线程运行 + // std::this_thread::sleep_for(std::chrono::milliseconds(10)); + if(abort_request) { + break; + } + if (seek_req) { + // seek的位置 + int64_t seek_target = seek_pos; + int64_t seek_min = seek_rel > 0 ? seek_target - seek_rel + 2 : INT64_MIN; + int64_t seek_max = seek_rel < 0 ? seek_target - seek_rel - 2 : INT64_MAX; + ret = avformat_seek_file(ic, -1, seek_min, seek_target, seek_max, seek_flags); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, + "%s: error while seeking\n", ic->filename); + } else { + if (audio_stream >= 0) { //有audio流 + packet_queue_flush(&audioq); + packet_queue_put(&audioq, &flush_pkt); + } + if (video_stream >= 0) { //有video流 + packet_queue_flush(&videoq); + packet_queue_put(&videoq, &flush_pkt); + } + } + seek_req = 0; + eof = 0; + ffp_notify_msg1(this, FFP_MSG_SEEK_COMPLETE); + } + /* if the queue are full, no need to read more */ + if (infinite_buffer < 1 && + (audioq.size + videoq.size > MAX_QUEUE_SIZE + || (stream_has_enough_packets(audio_st, audio_stream, &audioq) && + stream_has_enough_packets(video_st, video_stream, &videoq) ))) { + /* wait 10 ms */ + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + // 7.读取媒体数据,得到的是音视频分离后、解码前的数据 + ret = av_read_frame(ic, pkt); // 调用不会释放pkt的数据,需要我们自己去释放packet的数据 + if(ret < 0) { // 出错或者已经读取完毕了 + if ((ret == AVERROR_EOF || avio_feof(ic->pb)) && !eof) { // 读取完毕了 + // 刷空包给队列 + if (video_stream >= 0) { + packet_queue_put_nullpacket(&videoq, video_stream); + } + if (audio_stream >= 0) { + packet_queue_put_nullpacket(&audioq, audio_stream); + } + eof = 1; + } + if (ic->pb && ic->pb->error) { // io异常 // 退出循环 + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 读取完数据了,这里可以使用timeout的方式休眠等待下一步的检测 + continue; // 继续循环 + } else { + eof = 0; + } + // 插入队列 先只处理音频包 + if (pkt->stream_index == audio_stream) { + // LOG(INFO) << "audio ===== pkt pts:" << pkt->pts << ", dts:" << pkt->dts; + packet_queue_put(&audioq, pkt); + } else if (pkt->stream_index == video_stream) { + // LOG(INFO) << "video ===== pkt pts:" << pkt->pts << ", dts:" << pkt->dts; + packet_queue_put(&videoq, pkt); + } else { + av_packet_unref(pkt);// // 不入队列则直接释放数据 + } + } + LOG(INFO) << " leave"; +fail: + return 0; +} +/* polls for possible required screen refresh at least this often, should be less than 1/fps */ +#define REFRESH_RATE 0.01 // 每帧休眠10ms +int FFPlayer::video_refresh_thread() +{ + double remaining_time = 0.0; + while (!abort_request) { + if (remaining_time > 0.0) { + av_usleep((int)(int64_t)(remaining_time * 1000000.0)); + } + remaining_time = REFRESH_RATE; + video_refresh(&remaining_time); + } + LOG(INFO) << " leave" ; + return 0; +} +double FFPlayer::vp_duration( Frame * vp, Frame * nextvp) +{ + if (vp->serial == nextvp->serial) { + double duration = nextvp->pts - vp->pts; + if (std::isnan(duration) || duration <= 0 || duration > max_frame_duration) { + return vp->duration / pf_playback_rate; + } else { + return duration / pf_playback_rate; + } + } else { + return 0.0; + } +} +double FFPlayer::compute_target_delay(double delay) +{ + double sync_threshold, diff = 0; + /* update delay to follow master synchronisation source */ + if (get_master_sync_type() != AV_SYNC_VIDEO_MASTER) { + /* if video is slave, we try to correct big delays by + duplicating or deleting a frame */ + diff = get_clock(&vidclk) - get_master_clock(); + /* skip or repeat frame. We take into account the + delay to compute the threshold. I still don't know + if it is the best guess */ + sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay)); + if (! std::isnan(diff) && fabs(diff) < max_frame_duration) { + if (diff <= -sync_threshold) { + delay = FFMAX(0, delay + diff); + } else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) { + delay = delay + diff; + } else if (diff >= sync_threshold) { + delay = 2 * delay; + } + } + } + av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n", + delay, -diff); + return delay; +} +void FFPlayer::update_video_pts(double pts, int64_t pos, int serial) +{ + /* update current video pts */ + set_clock(&vidclk, pts / pf_playback_rate, serial); +} +void FFPlayer::video_refresh(double * remaining_time) +{ + Frame *vp = nullptr, *lastvp = nullptr; + // 目前我们先是只有队列里面有视频帧可以播放,就先播放出来 + // 判断有没有视频画面 + if (video_st) { +retry: + if (frame_queue_nb_remaining(&pictq) == 0) { + // nothing to do, no picture to display in the queue + video_no_data = 1; // 没有数据可读 + if(eof == 1) { + check_play_finish(); + } + } else { + video_no_data = 0; // 有数据可读 + double last_duration, duration, delay; + /* dequeue the picture */ + lastvp = frame_queue_peek_last(&pictq); + screenshot(lastvp->frame); + vp = frame_queue_peek(&pictq); + if (vp->serial != videoq.serial) { + frame_queue_next(&pictq); + goto retry; + } + if (lastvp->serial != vp->serial) { + frame_timer = av_gettime_relative() / 1000000.0; + } + if (paused) { + goto display; + } + /* compute nominal last_duration */ + last_duration = vp_duration(lastvp, vp); + // LOG(INFO) << "last_duration ......" << last_duration; + delay = compute_target_delay(last_duration); + // LOG(INFO) << "delay ......" << delay; + double time = av_gettime_relative() / 1000000.0; + if (time < frame_timer + delay) { + // LOG(INFO) << "(frame_timer + delay) - time " << frame_timer + delay - time; + *remaining_time = FFMIN( frame_timer + delay - time, *remaining_time); + goto display; + } + frame_timer += delay; + if (delay > 0 && time - frame_timer > AV_SYNC_THRESHOLD_MAX) { + frame_timer = time; + } + SDL_LockMutex(pictq.mutex); + if (!std::isnan(vp->pts)) { + update_video_pts(vp->pts, vp->pos, vp->serial); + } + SDL_UnlockMutex(pictq.mutex); + // LOG(INFO) << "debug " << __LINE__; + if (frame_queue_nb_remaining(&pictq) > 1) { + Frame *nextvp = frame_queue_peek_next(&pictq); + duration = vp_duration(vp, nextvp); + if (!step && (framedrop > 0 || (framedrop && get_master_sync_type() != AV_SYNC_VIDEO_MASTER)) + && time > frame_timer + duration) { + frame_drops_late++; + // LOG(INFO) << "frame_drops_late " << frame_drops_late; + frame_queue_next(&pictq); + goto retry; + } + } + frame_queue_next(&pictq); + force_refresh = 1; + // LOG(INFO) << "debug " << __LINE__; + // if (step && !paused) + // stream_toggle_pause(is); + } +display: + /* display picture */ + if (force_refresh && pictq.rindex_shown) { + if(vp) { + if(video_refresh_callback_) { + video_refresh_callback_(vp); + } + } + } + } + force_refresh = 0; +} +void FFPlayer::AddVideoRefreshCallback( + std::function callback) +{ + video_refresh_callback_ = callback; +} +int FFPlayer::get_master_sync_type() +{ + if (av_sync_type == AV_SYNC_VIDEO_MASTER) { + if (video_st) { + return AV_SYNC_VIDEO_MASTER; + } else { + return AV_SYNC_AUDIO_MASTER; /* 如果没有视频成分则使用 audio master */ + } + } else if (av_sync_type == AV_SYNC_AUDIO_MASTER) { + if (audio_st) { + return AV_SYNC_AUDIO_MASTER; + } else if(video_st) { + return AV_SYNC_VIDEO_MASTER; // 只有音频的存在 + } else { + return AV_SYNC_UNKNOW_MASTER; + } + } else { + return AV_SYNC_AUDIO_MASTER; + } +} +double FFPlayer::get_master_clock() +{ + double val; + switch (get_master_sync_type()) { + case AV_SYNC_VIDEO_MASTER: + val = get_clock(&vidclk); + break; + case AV_SYNC_AUDIO_MASTER: + val = get_clock(&audclk); + break; + default: + val = get_clock(&audclk); // 这里我们不支持以外部时钟为基准的方式 + break; + } + return val; +} +Decoder::Decoder() +{ + av_init_packet(&pkt_); +} +Decoder::~Decoder() +{ +} +void Decoder::decoder_init(AVCodecContext * avctx, PacketQueue * queue) +{ + avctx_ = avctx; + queue_ = queue; +} +int Decoder::decoder_start(AVMediaType codec_type, const char *thread_name, void *arg) +{ + // 启用包队列 + packet_queue_start(queue_); + // 创建线程 + if(AVMEDIA_TYPE_VIDEO == codec_type) { + decoder_thread_ = new std::thread(&Decoder::video_thread, this, arg); + } else if (AVMEDIA_TYPE_AUDIO == codec_type) { + decoder_thread_ = new std::thread(&Decoder::audio_thread, this, arg); + } else { + return -1; + } + return 0; +} +void Decoder::decoder_abort(FrameQueue * fq) +{ + packet_queue_abort(queue_); // 请求退出包队列 + frame_queue_signal(fq); // 唤醒阻塞的帧队列 + if(decoder_thread_ && decoder_thread_->joinable()) { + decoder_thread_->join(); // 等待解码线程退出 + delete decoder_thread_; + decoder_thread_ = NULL; + } + packet_queue_flush(queue_); // 情况packet队列,并释放数据 +} +void Decoder::decoder_destroy() +{ + av_packet_unref(&pkt_); + avcodec_free_context(&avctx_); +} +// 返回值-1: 请求退出 +// 0: 解码已经结束了,不再有数据可以读取 +// 1: 获取到解码后的frame +int Decoder::decoder_decode_frame(AVFrame * frame) +{ + int ret = AVERROR(EAGAIN); + for (;;) { + AVPacket pkt; + // 1. 流连续情况下获取解码后的帧 + if (queue_->serial == pkt_serial_) { // 1.1 先判断是否是同一播放序列的数据 + do { + if (queue_->abort_request) { + return -1; // 是否请求退出 + } + // 1.2. 获取解码帧 + switch (avctx_->codec_type) { + case AVMEDIA_TYPE_VIDEO: + ret = avcodec_receive_frame(avctx_, frame); + if (ret >= 0) { + if (decoder_reorder_pts == -1) { + frame->pts = frame->best_effort_timestamp; + } else if (!decoder_reorder_pts) { + frame->pts = frame->pkt_dts; + } + // LOG(INFO) << "video frame pts:" << frame->pts << ", dts:" << frame->pkt_dts; + } + break; + case AVMEDIA_TYPE_AUDIO: + ret = avcodec_receive_frame(avctx_, frame); + if (ret >= 0) { + // LOG(INFO) << "audio frame pts:" << frame->pts << ", dts:" << frame->pkt_dts; + AVRational tb = {1, frame->sample_rate}; // + if (frame->pts != AV_NOPTS_VALUE) { + // 如果frame->pts正常则先将其从pkt_timebase转成{1, frame->sample_rate} + // pkt_timebase实质就是stream->time_base + frame->pts = av_rescale_q(frame->pts, avctx_->pkt_timebase, tb); + } else if (next_pts != AV_NOPTS_VALUE) { + // 如果frame->pts不正常则使用上一帧更新的next_pts和next_pts_tb + // 转成{1, frame->sample_rate} + frame->pts = av_rescale_q(next_pts, next_pts_tb, tb); + } + if (frame->pts != AV_NOPTS_VALUE) { + // 根据当前帧的pts和nb_samples预估下一帧的pts + next_pts = frame->pts + frame->nb_samples; + next_pts_tb = tb; // 设置timebase + } + } + break; + } + // 1.3. 检查解码是否已经结束,解码结束返回0 + if (ret == AVERROR_EOF) { + finished_ = pkt_serial_; + LOG(INFO) << "avcodec_flush_buffers pkt_serial:" << pkt_serial_; + avcodec_flush_buffers(avctx_); + return 0; + } + // 1.4. 正常解码返回1 + if (ret >= 0) { + return 1; + } + } while (ret != AVERROR(EAGAIN)); // 1.5 没帧可读时ret返回EAGIN,需要继续送packet + } + // 2 获取一个packet,如果播放序列不一致(数据不连续)则过滤掉“过时”的packet + do { + // 2.1 如果没有数据可读则唤醒read_thread, 实际是continue_read_thread SDL_cond + // if (queue_->nb_packets == 0) // 没有数据可读 + // SDL_CondSignal(empty_queue_cond);// 通知read_thread放入packet + // 2.2 如果还有pending的packet则使用它 + if (packet_pending_) { + av_packet_move_ref(&pkt, &pkt_); + packet_pending_ = 0; + } else { + // 2.3 阻塞式读取packet + if (packet_queue_get(queue_, &pkt, 1, &pkt_serial_) < 0) { + return -1; + } + } + if(queue_->serial != pkt_serial_) { + // darren自己的代码 + LOG(INFO) << "discontinue:queue->serial:" << queue_->serial << ", pkt_serial:" << pkt_serial_; + av_packet_unref(&pkt); // fixed me? 释放要过滤的packet + } + } while (queue_->serial != pkt_serial_);// 如果不是同一播放序列(流不连续)则继续读取 + // 3 将packet送入解码器 + if (pkt.data == flush_pkt.data) {// + // when seeking or when switching to a different stream + avcodec_flush_buffers(avctx_); //清空里面的缓存帧 + finished_ = 0; // 重置为0 + next_pts = start_pts; // 主要用在了audio + next_pts_tb = start_pts_tb;// 主要用在了audio + } else { + if (avctx_->codec_type == AVMEDIA_TYPE_SUBTITLE) { + // int got_frame = 0; + // ret = avcodec_decode_subtitle2(avctx_, sub, &got_frame, &pkt); + // if (ret < 0) { + // ret = AVERROR(EAGAIN); + // } else { + // if (got_frame && !pkt.data) { + // packet_pending = 1; + // av_packet_move_ref(&pkt, &pkt); + // } + // ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF); + // } + } else { + if (avcodec_send_packet(avctx_, &pkt) == AVERROR(EAGAIN)) { + // av_log(avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); + packet_pending_ = 1; + av_packet_move_ref(&pkt_, &pkt); + } + } + av_packet_unref(&pkt); // 一定要自己去释放音视频数据 + } + } +} +int Decoder::get_video_frame(AVFrame * frame) +{ + int got_picture; + // 1. 获取解码后的视频帧 + if ((got_picture = decoder_decode_frame(frame)) < 0) { + return -1; // 返回-1意味着要退出解码线程, 所以要分析decoder_decode_frame什么情况下返回-1 + } + if (got_picture) { + // 2. 分析获取到的该帧是否要drop掉, 该机制的目的是在放入帧队列前先drop掉过时的视频帧 + // frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(ic, video_st, frame); + } + return got_picture; +} +int Decoder::queue_picture(FrameQueue * fq, AVFrame * src_frame, double pts, double duration, int64_t pos, int serial) +{ + Frame *vp; + if (!(vp = frame_queue_peek_writable(fq))) { // 检测队列是否有可写空间 + return -1; // 请求退出则返回-1 + } + // 执行到这步说已经获取到了可写入的Frame + // vp->sar = src_frame->sample_aspect_ratio; + // vp->uploaded = 0; + vp->width = src_frame->width; + vp->height = src_frame->height; + vp->format = src_frame->format; + vp->pts = pts; + vp->duration = duration; + vp->pos = pos; + vp->serial = serial; // 设置serial + av_frame_move_ref(vp->frame, src_frame); // 将src中所有数据转移到dst中,并复位src。 + frame_queue_push(fq); // 更新写索引位置 + return 0; +} +int Decoder::audio_thread(void *arg) +{ + LOG(INFO) << " into " ; + FFPlayer *is = (FFPlayer *)arg; + AVFrame *frame = av_frame_alloc(); // 分配解码帧 + Frame *af; + int got_frame = 0; // 是否读取到帧 + AVRational tb; // timebase + int ret = 0; + if (!frame) { + return AVERROR(ENOMEM); + } + do { + // 获取缓存情况 + is->ffp_audio_statistic_l(); + // 1. 读取解码帧 + if ((got_frame = decoder_decode_frame(frame)) < 0) { // 是否获取到一帧数据 + goto the_end; // < =0 abort + } + // LOG(INFO) << avctx_->codec->name << " packet size: " << queue_->size << " frame size: " << pictq.size << ", pts: " << frame->pts ; + if (got_frame) { + /*tb = (AVRational) { + 1, frame->sample_rate + };*/ // 设置为sample_rate为timebase + tb.num = 1; + tb.den = frame->sample_rate; + // 2. 获取可写Frame + if (!(af = frame_queue_peek_writable(&is->sampq))) { // 获取可写帧 + goto the_end; + } + // 3. 设置Frame并放入FrameQueue + af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); // 转换时间戳 + af->pos = frame->pkt_pos; + af->serial = is->auddec.pkt_serial_; + AVRational temp_a; + temp_a.num = frame->nb_samples; + temp_a.den = frame->sample_rate; + af->duration = av_q2d(temp_a); +// af->duration = av_q2d((AVRational) { +// frame->nb_samples, frame->sample_rate +// }); + av_frame_move_ref(af->frame, frame); + frame_queue_push(&is->sampq); // 代表队列真正插入一帧数据 + } + } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF); +the_end: + LOG(INFO) << " leave " ; + av_frame_free(&frame); + return ret; +} +int Decoder::video_thread(void *arg) +{ + LOG(INFO) << " into " ; + FFPlayer *is = (FFPlayer *)arg; + AVFrame *frame = av_frame_alloc(); // 分配解码帧 + double pts; // pts + double duration; // 帧持续时间 + int ret; + //1 获取stream timebase + AVRational tb = is->video_st->time_base; // 获取stream timebase + //2 获取帧率,以便计算每帧picture的duration + AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL); + if (!frame) { + return AVERROR(ENOMEM); + } + for (;;) { // 循环取出视频解码的帧数据 + is->ffp_video_statistic_l();// 统计视频packet缓存 + // 3 获取解码后的视频帧 + ret = get_video_frame(frame); + if (ret < 0) { + goto the_end; //解码结束, 什么时候会结束 + } + if (!ret) { //没有解码得到画面, 什么情况下会得不到解后的帧 + continue; + } + // LOG(INFO) << avctx_->codec->name << " packet size: " << queue_->size << " frame size: " << pictq.size << ", pts: " << frame->pts ; + // 1/25 = 0.04秒 + // 4 计算帧持续时间和换算pts值为秒 + // 1/帧率 = duration 单位秒, 没有帧率时则设置为0, 有帧率帧计算出帧间隔 + AVRational temp_a; + temp_a.num = frame_rate.den; + temp_a.den = frame_rate.num; + duration = (frame_rate.num && frame_rate.den ? av_q2d(temp_a) : 0); +// duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational) { +// frame_rate.den, frame_rate.num +// }) : 0); + // 根据AVStream timebase计算出pts值, 单位为秒 + pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); // 单位为秒 + // 5 将解码后的视频帧插入队列 + ret = queue_picture(&is->pictq, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial_); + // 6 释放frame对应的数据 + av_frame_unref(frame); + if (ret < 0) { // 返回值小于0则退出线程 + goto the_end; + } + } +the_end: + LOG(INFO) << " leave " ; + av_frame_free(&frame); + return 0; +} diff --git a/ff_ffplay.h b/ff_ffplay.h new file mode 100644 index 0000000..3df2b04 --- /dev/null +++ b/ff_ffplay.h @@ -0,0 +1,234 @@ +#ifndef FF_FFPLAY_H +#define FF_FFPLAY_H +#include +#include +#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 video_refresh_callback_ = NULL; + void AddVideoRefreshCallback(std::function 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 diff --git a/ff_ffplay_def.cpp b/ff_ffplay_def.cpp new file mode 100644 index 0000000..5037c34 --- /dev/null +++ b/ff_ffplay_def.cpp @@ -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)); +} diff --git a/ff_ffplay_def.h b/ff_ffplay_def.h new file mode 100644 index 0000000..b4f89c7 --- /dev/null +++ b/ff_ffplay_def.h @@ -0,0 +1,259 @@ +#ifndef FF_FFPLAY_DEF_H +#define FF_FFPLAY_DEF_H + + +#include +#include +#include +#include +#include +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 +#include + +#include + +#include "ijksdl_timer.h" + +#define MAX_QUEUE_SIZE (15 * 1024 * 1024) +#define MIN_FRAMES 25 +#define EXTERNAL_CLOCK_MIN_FRAMES 2 +#define EXTERNAL_CLOCK_MAX_FRAMES 10 + +/* Step size for volume control in dB */ +#define SDL_VOLUME_STEP (0.75) + +/* no AV sync correction is done if below the minimum AV sync threshold */ +#define AV_SYNC_THRESHOLD_MIN 0.04 +/* AV sync correction is done if above the maximum AV sync threshold */ +#define AV_SYNC_THRESHOLD_MAX 0.1 +/* If a frame duration is longer than this, it will not be duplicated to compensate AV sync */ +#define AV_SYNC_FRAMEDUP_THRESHOLD 0.1 +/* no AV correction is done if too big error */ +#define AV_NOSYNC_THRESHOLD 10.0 + + +typedef struct FFTrackCacheStatistic +{ + int64_t duration; + int64_t bytes; + int64_t packets; +} FFTrackCacheStatistic; + + +typedef struct FFStatistic +{ + int64_t vdec_type; + + float vfps; + float vdps; + float avdelay; + float avdiff; + int64_t bit_rate; + + FFTrackCacheStatistic video_cache; + FFTrackCacheStatistic audio_cache; + + int64_t buf_backwards; + int64_t buf_forwards; + int64_t buf_capacity; + SDL_SpeedSampler2 tcp_read_sampler; + int64_t latest_seek_load_duration; + int64_t byte_count; + int64_t cache_physical_pos; + int64_t cache_file_forwards; + int64_t cache_file_pos; + int64_t cache_count_bytes; + int64_t logical_file_size; + int drop_frame_count; + int decode_frame_count; + float drop_frame_rate; +} FFStatistic; + +enum RET_CODE +{ + RET_ERR_UNKNOWN = -2, // 未知错误 + RET_FAIL = -1, // 失败 + RET_OK = 0, // 正常 + RET_ERR_OPEN_FILE, // 打开文件失败 + RET_ERR_NOT_SUPPORT, // 不支持 + RET_ERR_OUTOFMEMORY, // 没有内存 + RET_ERR_STACKOVERFLOW, // 溢出 + RET_ERR_NULLREFERENCE, // 空参考 + RET_ERR_ARGUMENTOUTOFRANGE, // + RET_ERR_PARAMISMATCH, // + RET_ERR_MISMATCH_CODE, // 没有匹配的编解码器 + RET_ERR_EAGAIN, + RET_ERR_EOF +}; + + +typedef struct MyAVPacketList { + AVPacket pkt; //解封装后的数据 + struct MyAVPacketList *next; //下一个节点 + int serial; //播放序列 +} MyAVPacketList; + +typedef struct PacketQueue { + MyAVPacketList *first_pkt, *last_pkt; // 队首,队尾指针 + int nb_packets; // 包数量,也就是队列元素数量 + int size; // 队列所有元素的数据大小总和 + int64_t duration; // 队列所有元素的数据播放持续时间 + int abort_request; // 用户退出请求标志 + int serial; // 播放序列号,和MyAVPacketList的serial作用相同,但改变的时序稍微有点不同 + SDL_mutex *mutex; // 用于维持PacketQueue的多线程安全(SDL_mutex可以按pthread_mutex_t理解) + SDL_cond *cond; // 用于读、写线程相互通知(SDL_cond可以按pthread_cond_t理解) +} PacketQueue; + +#define VIDEO_PICTURE_QUEUE_SIZE 3 // 图像帧缓存数量 +#define VIDEO_PICTURE_QUEUE_SIZE_MIN (3) +#define VIDEO_PICTURE_QUEUE_SIZE_MAX (16) +#define VIDEO_PICTURE_QUEUE_SIZE_DEFAULT (VIDEO_PICTURE_QUEUE_SIZE_MIN) +#define SUBPICTURE_QUEUE_SIZE 16 // 字幕帧缓存数量 +#define SAMPLE_QUEUE_SIZE 9 // 采样帧缓存数量 +#define FRAME_QUEUE_SIZE FFMAX(SAMPLE_QUEUE_SIZE, FFMAX(VIDEO_PICTURE_QUEUE_SIZE, SUBPICTURE_QUEUE_SIZE)) + + +typedef struct AudioParams { + int freq; // 采样率 + int channels; // 通道数 + int64_t channel_layout; // 通道布局,比如2.1声道,5.1声道等 + enum AVSampleFormat fmt; // 音频采样格式,比如AV_SAMPLE_FMT_S16表示为有符号16bit深度,交错排列模式。 + int frame_size; // 一个采样单元占用的字节数(比如2通道时,则左右通道各采样一次合成一个采样单元) + int bytes_per_sec; // 一秒时间的字节数,比如采样率48Khz,2 channel,16bit,则一秒48000*2*16/8=192000 +} AudioParams; + + +/* Common struct for handling all types of decoded data and allocated render buffers. */ +// 用于缓存解码后的数据 +typedef struct Frame { + AVFrame *frame; // 指向数据帧 + int serial; // 帧序列,在seek的操作时serial会变化 + double pts; // 时间戳,单位为秒 + double duration; // 该帧持续时间,单位为秒 + int64_t pos; + int width; // 图像宽度 + int height; // 图像高读 + int format; // 对于图像为(enum AVPixelFormat) + AVRational sar; + int uploaded; + int flip_v; +} Frame; + +/* 这是一个循环队列,windex是指其中的首元素,rindex是指其中的尾部元素. */ +typedef struct FrameQueue { + Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的内存,需要注意该值的设置 + int rindex; // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧 + int windex; // 写索引 + int size; // 当前总帧数 + int max_size; // 可存储最大帧数 + int keep_last; + int rindex_shown; + SDL_mutex *mutex; // 互斥量 + SDL_cond *cond; // 条件变量 + PacketQueue *pktq; // 数据包缓冲队列 +} FrameQueue; + + +// 这里讲的系统时钟 是通过av_gettime_relative()获取到的时钟,单位为微妙 +typedef struct Clock { + double pts; // 时钟基础, 当前帧(待播放)显示时间戳,播放后,当前帧变成上一帧 + // 当前pts与当前系统时钟的差值, audio、video对于该值是独立的 + double pts_drift; // clock base minus time at which we updated the clock + // 当前时钟(如视频时钟)最后一次更新时间,也可称当前时钟时间 + double last_updated; // 最后一次更新的系统时钟 + double speed; // 时钟速度控制,用于控制播放速度 + // 播放序列,所谓播放序列就是一段连续的播放动作,一个seek操作会启动一段新的播放序列 + int serial; // clock is based on a packet with this serial + int paused; // = 1 说明是暂停状态 + // 指向packet_serial + int *queue_serial; /* pointer to the current packet queue serial, used for obsolete clock detection */ +} Clock; + +/** + *音视频同步方式,缺省以音频为基准 + */ +enum { + AV_SYNC_UNKNOW_MASTER = -1, + AV_SYNC_AUDIO_MASTER, // 以音频为基准 + AV_SYNC_VIDEO_MASTER, // 以视频为基准 +// AV_SYNC_EXTERNAL_CLOCK, // 以外部时钟为基准,synchronize to an external clock */ +}; + +#define fftime_to_milliseconds(ts) (av_rescale(ts, 1000, AV_TIME_BASE)) +#define milliseconds_to_fftime(ms) (av_rescale(ms, AV_TIME_BASE, 1000)) + +extern AVPacket flush_pkt; +// 队列相关 +int packet_queue_put(PacketQueue *q, AVPacket *pkt); +int packet_queue_put_nullpacket(PacketQueue *q, int stream_index); +int packet_queue_init(PacketQueue *q); +void packet_queue_flush(PacketQueue *q); +void packet_queue_destroy(PacketQueue *q); +void packet_queue_abort(PacketQueue *q); +void packet_queue_start(PacketQueue *q); +int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial); + +/** + * @brief 获取帧缓存的数据可以播放的时间长度 + * @param q 队列本身 + * @param time_base 用于计算packet的时间戳转换 + * @param packet_duration 单个包可以播放的时长 + * @return 返回时长以秒为单位 + */ +double packet_queue_cache_duration(PacketQueue *q, AVRational time_base, double packet_duration); + +/* 初始化FrameQueue,视频和音频keep_last设置为1,字幕设置为0 */ +int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size, int keep_last); +void frame_queue_destory(FrameQueue *f); +void frame_queue_signal(FrameQueue *f); +/* 获取队列当前Frame, 在调用该函数前先调用frame_queue_nb_remaining确保有frame可读 */ +Frame *frame_queue_peek(FrameQueue *f); + +/* 获取当前Frame的下一Frame, 此时要确保queue里面至少有2个Frame */ +// 不管你什么时候调用,返回来肯定不是 NULL +Frame *frame_queue_peek_next(FrameQueue *f); +/* 获取last Frame: + */ +Frame *frame_queue_peek_last(FrameQueue *f); +// 获取可写指针 +Frame *frame_queue_peek_writable(FrameQueue *f); +// 获取可读 +Frame *frame_queue_peek_readable(FrameQueue *f); +// 更新写指针 +void frame_queue_push(FrameQueue *f); +/* 释放当前frame,并更新读索引rindex */ +void frame_queue_next(FrameQueue *f); +int frame_queue_nb_remaining(FrameQueue *f); +int64_t frame_queue_last_pos(FrameQueue *f); + + +// 时钟相关 +double get_clock(Clock *c); +void set_clock_at(Clock *c, double pts, int serial, double time); +void set_clock(Clock *c, double pts, int serial); +void init_clock(Clock *c, int *queue_serial); + + + void ffp_reset_statistic(FFStatistic *dcc); + + + +#endif // FF_FFPLAY_DEF_H diff --git a/ffmsg.h b/ffmsg.h new file mode 100644 index 0000000..4bea06c --- /dev/null +++ b/ffmsg.h @@ -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 diff --git a/ffmsg_queue.cpp b/ffmsg_queue.cpp new file mode 100644 index 0000000..52c23f5 --- /dev/null +++ b/ffmsg_queue.cpp @@ -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); +} diff --git a/ffmsg_queue.h b/ffmsg_queue.h new file mode 100644 index 0000000..386c50d --- /dev/null +++ b/ffmsg_queue.h @@ -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 diff --git a/globalhelper.cpp b/globalhelper.cpp new file mode 100644 index 0000000..2926062 --- /dev/null +++ b/globalhelper.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include + +#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); +} + diff --git a/globalhelper.h b/globalhelper.h new file mode 100644 index 0000000..78af05f --- /dev/null +++ b/globalhelper.h @@ -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 +#include +#include +#include +#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 diff --git a/homewindow.cpp b/homewindow.cpp new file mode 100644 index 0000000..c490fcc --- /dev/null +++ b/homewindow.cpp @@ -0,0 +1,752 @@ +#include "homewindow.h" +#include "ui_homewindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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"); + connect(this, &HomeWindow::sig_showTips, this, &HomeWindow::on_showTips); + qRegisterMetaType("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); +} + diff --git a/homewindow.h b/homewindow.h new file mode 100644 index 0000000..feff675 --- /dev/null +++ b/homewindow.h @@ -0,0 +1,146 @@ +#ifndef HOMEWINDOW_H +#define HOMEWINDOW_H + +#include +#include +#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 diff --git a/homewindow.ui b/homewindow.ui new file mode 100644 index 0000000..620e569 --- /dev/null +++ b/homewindow.ui @@ -0,0 +1,558 @@ + + + HomeWindow + + + + 0 + 0 + 1115 + 757 + + + + + 800 + 600 + + + + + 16777215 + 16777215 + + + + GDMP + + + + + + + + + 160 + 160 + 400 + 240 + + + + + 400 + 240 + + + + + + + + + + 720 + 160 + 240 + 240 + + + + + 240 + 0 + + + + + 240 + 16777215 + + + + + + + + + + + + + 0 + 0 + + + + + 800 + 128 + + + + + 16777215 + 95 + + + + + + + + + + 0 + 0 + + + + + 300 + 20 + + + + + 16777215 + 20 + + + + 65536 + + + Qt::Horizontal + + + + + + + + 65 + 16777215 + + + + 20:10:00 + + + + + + + + 5 + 16777215 + + + + / + + + + + + + + 65 + 16777215 + + + + 24:00:00 + + + + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 0 + 0 + + + + + 100 + 20 + + + + + 100 + 20 + + + + Qt::Horizontal + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1115 + 22 + + + + + 画面 + + + + 方向 + + + + + + + + 色调 + + + + + + + + + + + 文件 + + + + + + + + + + 打开文件 + + + + + 反转 + + + + + 暖色 + + + + + 冷色 + + + + + 旋转 + + + + + 翻转 + + + + + 正常 + + + + + 暖色 + + + + + 冷色 + + + + + 正常 + + + + + 打开网络串流 + + + + + + + DisplayWind + QWidget +
displaywind.h
+ 1 +
+ + Playlist + QWidget +
playlist.h
+ 1 +
+ + CustomSlider + QSlider +
customslider.h
+
+
+ + +
diff --git a/ijkmediaplayer.cpp b/ijkmediaplayer.cpp new file mode 100644 index 0000000..dc4b9fa --- /dev/null +++ b/ijkmediaplayer.cpp @@ -0,0 +1,260 @@ +#include "ijkmediaplayer.h" +#include +#include +#include "ffmsg.h" +#include "easylogging++.h" +IjkMediaPlayer::IjkMediaPlayer() +{ + LOG(INFO) << " IjkMediaPlayer()\n "; +} + +IjkMediaPlayer::~IjkMediaPlayer() +{ + LOG(INFO) << " ~IjkMediaPlayer()\n "; + // 此时需要停止线程 +} + +int IjkMediaPlayer::ijkmp_create(std::function 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 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); +} + + + + + + + diff --git a/ijkmediaplayer.h b/ijkmediaplayer.h new file mode 100644 index 0000000..c91cae7 --- /dev/null +++ b/ijkmediaplayer.h @@ -0,0 +1,193 @@ +#ifndef IJKMEDIAPLAYER_H +#define IJKMEDIAPLAYER_H + +#include +#include +#include +#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 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 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 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 diff --git a/ijksdl_timer.cpp b/ijksdl_timer.cpp new file mode 100644 index 0000000..8e24afd --- /dev/null +++ b/ijksdl_timer.cpp @@ -0,0 +1,6 @@ +#include "ijksdl_timer.h" + +ijksdl_timer::ijksdl_timer() +{ + +} diff --git a/ijksdl_timer.h b/ijksdl_timer.h new file mode 100644 index 0000000..c937597 --- /dev/null +++ b/ijksdl_timer.h @@ -0,0 +1,20 @@ +#ifndef IJKSDL_TIMER_H +#define IJKSDL_TIMER_H + +#include +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 diff --git a/imagescaler.h b/imagescaler.h new file mode 100644 index 0000000..09f2822 --- /dev/null +++ b/imagescaler.h @@ -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 diff --git a/log/easylogging++.cc b/log/easylogging++.cc new file mode 100644 index 0000000..0151f21 --- /dev/null +++ b/log/easylogging++.cc @@ -0,0 +1,3120 @@ +// +// Bismillah ar-Rahmaan ar-Raheem +// +// Easylogging++ v9.96.7 +// Cross-platform logging library for C++ applications +// +// Copyright (c) 2012-2018 Amrayn Web Services +// Copyright (c) 2012-2018 @abumusamq +// +// This library is released under the MIT Licence. +// https://github.com/amrayn/easyloggingpp/blob/master/LICENSE +// +// https://amrayn.com +// http://muflihun.com +// + +#include "easylogging++.h" + +#if defined(AUTO_INITIALIZE_EASYLOGGINGPP) +INITIALIZE_EASYLOGGINGPP +#endif + +namespace el { + +// el::base +namespace base { +// el::base::consts +namespace consts { + +// Level log values - These are values that are replaced in place of %level format specifier +// Extra spaces after format specifiers are only for readability purposes in log files +static const base::type::char_t* kInfoLevelLogValue = ELPP_LITERAL("INFO"); +static const base::type::char_t* kDebugLevelLogValue = ELPP_LITERAL("DEBUG"); +static const base::type::char_t* kWarningLevelLogValue = ELPP_LITERAL("WARNING"); +static const base::type::char_t* kErrorLevelLogValue = ELPP_LITERAL("ERROR"); +static const base::type::char_t* kFatalLevelLogValue = ELPP_LITERAL("FATAL"); +static const base::type::char_t* kVerboseLevelLogValue = + ELPP_LITERAL("VERBOSE"); // will become VERBOSE-x where x = verbose level +static const base::type::char_t* kTraceLevelLogValue = ELPP_LITERAL("TRACE"); +static const base::type::char_t* kInfoLevelShortLogValue = ELPP_LITERAL("I"); +static const base::type::char_t* kDebugLevelShortLogValue = ELPP_LITERAL("D"); +static const base::type::char_t* kWarningLevelShortLogValue = ELPP_LITERAL("W"); +static const base::type::char_t* kErrorLevelShortLogValue = ELPP_LITERAL("E"); +static const base::type::char_t* kFatalLevelShortLogValue = ELPP_LITERAL("F"); +static const base::type::char_t* kVerboseLevelShortLogValue = ELPP_LITERAL("V"); +static const base::type::char_t* kTraceLevelShortLogValue = ELPP_LITERAL("T"); +// Format specifiers - These are used to define log format +static const base::type::char_t* kAppNameFormatSpecifier = ELPP_LITERAL("%app"); +static const base::type::char_t* kLoggerIdFormatSpecifier = ELPP_LITERAL("%logger"); +static const base::type::char_t* kThreadIdFormatSpecifier = ELPP_LITERAL("%thread"); +static const base::type::char_t* kSeverityLevelFormatSpecifier = ELPP_LITERAL("%level"); +static const base::type::char_t* kSeverityLevelShortFormatSpecifier = ELPP_LITERAL("%levshort"); +static const base::type::char_t* kDateTimeFormatSpecifier = ELPP_LITERAL("%datetime"); +static const base::type::char_t* kLogFileFormatSpecifier = ELPP_LITERAL("%file"); +static const base::type::char_t* kLogFileBaseFormatSpecifier = ELPP_LITERAL("%fbase"); +static const base::type::char_t* kLogLineFormatSpecifier = ELPP_LITERAL("%line"); +static const base::type::char_t* kLogLocationFormatSpecifier = ELPP_LITERAL("%loc"); +static const base::type::char_t* kLogFunctionFormatSpecifier = ELPP_LITERAL("%func"); +static const base::type::char_t* kCurrentUserFormatSpecifier = ELPP_LITERAL("%user"); +static const base::type::char_t* kCurrentHostFormatSpecifier = ELPP_LITERAL("%host"); +static const base::type::char_t* kMessageFormatSpecifier = ELPP_LITERAL("%msg"); +static const base::type::char_t* kVerboseLevelFormatSpecifier = ELPP_LITERAL("%vlevel"); +static const char* kDateTimeFormatSpecifierForFilename = "%datetime"; +// Date/time +static const char* kDays[7] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +static const char* kDaysAbbrev[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +static const char* kMonths[12] = { "January", "February", "March", "April", "May", "June", "July", "August", + "September", "October", "November", "December" + }; +static const char* kMonthsAbbrev[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static const char* kDefaultDateTimeFormat = "%Y-%M-%d %H:%m:%s,%g"; +static const char* kDefaultDateTimeFormatInFilename = "%Y-%M-%d_%H-%m"; +static const int kYearBase = 1900; +static const char* kAm = "AM"; +static const char* kPm = "PM"; +// Miscellaneous constants + +static const char* kNullPointer = "nullptr"; +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED +static const base::type::VerboseLevel kMaxVerboseLevel = 9; +static const char* kUnknownUser = "unknown-user"; +static const char* kUnknownHost = "unknown-host"; + + +//---------------- DEFAULT LOG FILE ----------------------- + +#if defined(ELPP_NO_DEFAULT_LOG_FILE) +# if ELPP_OS_UNIX +static const char* kDefaultLogFile = "/dev/null"; +# elif ELPP_OS_WINDOWS +static const char* kDefaultLogFile = "nul"; +# endif // ELPP_OS_UNIX +#elif defined(ELPP_DEFAULT_LOG_FILE) +static const char* kDefaultLogFile = ELPP_DEFAULT_LOG_FILE; +#else +static const char* kDefaultLogFile = "myeasylog.log"; +#endif // defined(ELPP_NO_DEFAULT_LOG_FILE) + + +#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) +static const char* kDefaultLogFileParam = "--default-log-file"; +#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) +#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) +static const char* kLoggingFlagsParam = "--logging-flags"; +#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) +static const char* kValidLoggerIdSymbols = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"; +static const char* kConfigurationComment = "##"; +static const char* kConfigurationLevel = "*"; +static const char* kConfigurationLoggerId = "--"; +} +// el::base::utils +namespace utils { + +/// @brief Aborts application due with user-defined status +static void abort(int status, const std::string& reason) { + // Both status and reason params are there for debugging with tools like gdb etc + ELPP_UNUSED(status); + ELPP_UNUSED(reason); +#if defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) + // Ignore msvc critical error dialog - break instead (on debug mode) + _asm int 3 +#else + ::abort(); +#endif // defined(ELPP_COMPILER_MSVC) && defined(_M_IX86) && defined(_DEBUG) +} + +} // namespace utils +} // namespace base + +// el + +// LevelHelper + +const char* LevelHelper::convertToString(Level level) { + // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. + if (level == Level::Global) return "GLOBAL"; + if (level == Level::Debug) return "DEBUG"; + if (level == Level::Info) return "INFO"; + if (level == Level::Warning) return "WARNING"; + if (level == Level::Error) return "ERROR"; + if (level == Level::Fatal) return "FATAL"; + if (level == Level::Verbose) return "VERBOSE"; + if (level == Level::Trace) return "TRACE"; + return "UNKNOWN"; +} + +struct StringToLevelItem { + const char* levelString; + Level level; +}; + +static struct StringToLevelItem stringToLevelMap[] = { + { "global", Level::Global }, + { "debug", Level::Debug }, + { "info", Level::Info }, + { "warning", Level::Warning }, + { "error", Level::Error }, + { "fatal", Level::Fatal }, + { "verbose", Level::Verbose }, + { "trace", Level::Trace } +}; + +Level LevelHelper::convertFromString(const char* levelStr) { + for (auto& item : stringToLevelMap) { + if (base::utils::Str::cStringCaseEq(levelStr, item.levelString)) { + return item.level; + } + } + return Level::Unknown; +} + +void LevelHelper::forEachLevel(base::type::EnumType* startIndex, const std::function& fn) { + base::type::EnumType lIndexMax = LevelHelper::kMaxValid; + do { + if (fn()) { + break; + } + *startIndex = static_cast(*startIndex << 1); + } while (*startIndex <= lIndexMax); +} + +// ConfigurationTypeHelper + +const char* ConfigurationTypeHelper::convertToString(ConfigurationType configurationType) { + // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. + if (configurationType == ConfigurationType::Enabled) return "ENABLED"; + if (configurationType == ConfigurationType::Filename) return "FILENAME"; + if (configurationType == ConfigurationType::Format) return "FORMAT"; + if (configurationType == ConfigurationType::ToFile) return "TO_FILE"; + if (configurationType == ConfigurationType::ToStandardOutput) return "TO_STANDARD_OUTPUT"; + if (configurationType == ConfigurationType::SubsecondPrecision) return "SUBSECOND_PRECISION"; + if (configurationType == ConfigurationType::PerformanceTracking) return "PERFORMANCE_TRACKING"; + if (configurationType == ConfigurationType::MaxLogFileSize) return "MAX_LOG_FILE_SIZE"; + if (configurationType == ConfigurationType::LogFlushThreshold) return "LOG_FLUSH_THRESHOLD"; + return "UNKNOWN"; +} + +struct ConfigurationStringToTypeItem { + const char* configString; + ConfigurationType configType; +}; + +static struct ConfigurationStringToTypeItem configStringToTypeMap[] = { + { "enabled", ConfigurationType::Enabled }, + { "to_file", ConfigurationType::ToFile }, + { "to_standard_output", ConfigurationType::ToStandardOutput }, + { "format", ConfigurationType::Format }, + { "filename", ConfigurationType::Filename }, + { "subsecond_precision", ConfigurationType::SubsecondPrecision }, + { "milliseconds_width", ConfigurationType::MillisecondsWidth }, + { "performance_tracking", ConfigurationType::PerformanceTracking }, + { "max_log_file_size", ConfigurationType::MaxLogFileSize }, + { "log_flush_threshold", ConfigurationType::LogFlushThreshold }, +}; + +ConfigurationType ConfigurationTypeHelper::convertFromString(const char* configStr) { + for (auto& item : configStringToTypeMap) { + if (base::utils::Str::cStringCaseEq(configStr, item.configString)) { + return item.configType; + } + } + return ConfigurationType::Unknown; +} + +void ConfigurationTypeHelper::forEachConfigType(base::type::EnumType* startIndex, const std::function& fn) { + base::type::EnumType cIndexMax = ConfigurationTypeHelper::kMaxValid; + do { + if (fn()) { + break; + } + *startIndex = static_cast(*startIndex << 1); + } while (*startIndex <= cIndexMax); +} + +// Configuration + +Configuration::Configuration(const Configuration& c) : + m_level(c.m_level), + m_configurationType(c.m_configurationType), + m_value(c.m_value) { +} + +Configuration& Configuration::operator=(const Configuration& c) { + if (&c != this) { + m_level = c.m_level; + m_configurationType = c.m_configurationType; + m_value = c.m_value; + } + return *this; +} + +/// @brief Full constructor used to sets value of configuration +Configuration::Configuration(Level level, ConfigurationType configurationType, const std::string& value) : + m_level(level), + m_configurationType(configurationType), + m_value(value) { +} + +void Configuration::log(el::base::type::ostream_t& os) const { + os << LevelHelper::convertToString(m_level) + << ELPP_LITERAL(" ") << ConfigurationTypeHelper::convertToString(m_configurationType) + << ELPP_LITERAL(" = ") << m_value.c_str(); +} + +/// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. +Configuration::Predicate::Predicate(Level level, ConfigurationType configurationType) : + m_level(level), + m_configurationType(configurationType) { +} + +bool Configuration::Predicate::operator()(const Configuration* conf) const { + return ((conf != nullptr) && (conf->level() == m_level) && (conf->configurationType() == m_configurationType)); +} + +// Configurations + +Configurations::Configurations(void) : + m_configurationFile(std::string()), + m_isFromFile(false) { +} + +Configurations::Configurations(const std::string& configurationFile, bool useDefaultsForRemaining, + Configurations* base) : + m_configurationFile(configurationFile), + m_isFromFile(false) { + parseFromFile(configurationFile, base); + if (useDefaultsForRemaining) { + setRemainingToDefault(); + } +} + +bool Configurations::parseFromFile(const std::string& configurationFile, Configurations* base) { + // We initial assertion with true because if we have assertion disabled, we want to pass this + // check and if assertion is enabled we will have values re-assigned any way. + bool assertionPassed = true; + ELPP_ASSERT((assertionPassed = base::utils::File::pathExists(configurationFile.c_str(), true)) == true, + "Configuration file [" << configurationFile << "] does not exist!"); + if (!assertionPassed) { + return false; + } + bool success = Parser::parseFromFile(configurationFile, this, base); + m_isFromFile = success; + return success; +} + +bool Configurations::parseFromText(const std::string& configurationsString, Configurations* base) { + bool success = Parser::parseFromText(configurationsString, this, base); + if (success) { + m_isFromFile = false; + } + return success; +} + +void Configurations::setFromBase(Configurations* base) { + if (base == nullptr || base == this) { + return; + } + base::threading::ScopedLock scopedLock(base->lock()); + for (Configuration*& conf : base->list()) { + set(conf); + } +} + +bool Configurations::hasConfiguration(ConfigurationType configurationType) { + base::type::EnumType lIndex = LevelHelper::kMinValid; + bool result = false; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + if (hasConfiguration(LevelHelper::castFromInt(lIndex), configurationType)) { + result = true; + } + return result; + }); + return result; +} + +bool Configurations::hasConfiguration(Level level, ConfigurationType configurationType) { + base::threading::ScopedLock scopedLock(lock()); +#if ELPP_COMPILER_INTEL + // We cant specify template types here, Intel C++ throws compilation error + // "error: type name is not allowed" + return RegistryWithPred::get(level, configurationType) != nullptr; +#else + return RegistryWithPred::get(level, configurationType) != nullptr; +#endif // ELPP_COMPILER_INTEL +} + +void Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) { + base::threading::ScopedLock scopedLock(lock()); + unsafeSet(level, configurationType, value); // This is not unsafe anymore as we have locked mutex + if (level == Level::Global) { + unsafeSetGlobally(configurationType, value, false); // Again this is not unsafe either + } +} + +void Configurations::set(Configuration* conf) { + if (conf == nullptr) { + return; + } + set(conf->level(), conf->configurationType(), conf->value()); +} + +void Configurations::setToDefault(void) { + setGlobally(ConfigurationType::Enabled, std::string("true"), true); + setGlobally(ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile), true); +#if defined(ELPP_NO_LOG_TO_FILE) + setGlobally(ConfigurationType::ToFile, std::string("false"), true); +#else + setGlobally(ConfigurationType::ToFile, std::string("true"), true); +#endif // defined(ELPP_NO_LOG_TO_FILE) + setGlobally(ConfigurationType::ToStandardOutput, std::string("true"), true); + setGlobally(ConfigurationType::SubsecondPrecision, std::string("3"), true); + setGlobally(ConfigurationType::PerformanceTracking, std::string("true"), true); + setGlobally(ConfigurationType::MaxLogFileSize, std::string("0"), true); + setGlobally(ConfigurationType::LogFlushThreshold, std::string("0"), true); + + setGlobally(ConfigurationType::Format, std::string("%datetime %level [%logger] %msg"), true); + set(Level::Debug, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); + // INFO and WARNING are set to default by Level::Global + set(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + set(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + set(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); + set(Level::Trace, ConfigurationType::Format, std::string("%datetime %level [%logger] [%func] [%loc] %msg")); +} + +void Configurations::setRemainingToDefault(void) { + base::threading::ScopedLock scopedLock(lock()); +#if defined(ELPP_NO_LOG_TO_FILE) + unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("false")); +#else + unsafeSetIfNotExist(Level::Global, ConfigurationType::Enabled, std::string("true")); +#endif // defined(ELPP_NO_LOG_TO_FILE) + unsafeSetIfNotExist(Level::Global, ConfigurationType::Filename, std::string(base::consts::kDefaultLogFile)); + unsafeSetIfNotExist(Level::Global, ConfigurationType::ToStandardOutput, std::string("true")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::SubsecondPrecision, std::string("3")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::PerformanceTracking, std::string("true")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::MaxLogFileSize, std::string("0")); + unsafeSetIfNotExist(Level::Global, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Debug, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%user@%host] [%func] [%loc] %msg")); + // INFO and WARNING are set to default by Level::Global + unsafeSetIfNotExist(Level::Error, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Fatal, ConfigurationType::Format, std::string("%datetime %level [%logger] %msg")); + unsafeSetIfNotExist(Level::Verbose, ConfigurationType::Format, std::string("%datetime %level-%vlevel [%logger] %msg")); + unsafeSetIfNotExist(Level::Trace, ConfigurationType::Format, + std::string("%datetime %level [%logger] [%func] [%loc] %msg")); +} + +bool Configurations::Parser::parseFromFile(const std::string& configurationFile, Configurations* sender, + Configurations* base) { + sender->setFromBase(base); + std::ifstream fileStream_(configurationFile.c_str(), std::ifstream::in); + ELPP_ASSERT(fileStream_.is_open(), "Unable to open configuration file [" << configurationFile << "] for parsing."); + bool parsedSuccessfully = false; + std::string line = std::string(); + Level currLevel = Level::Unknown; + std::string currConfigStr = std::string(); + std::string currLevelStr = std::string(); + while (fileStream_.good()) { + std::getline(fileStream_, line); + parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); + ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); + } + return parsedSuccessfully; +} + +bool Configurations::Parser::parseFromText(const std::string& configurationsString, Configurations* sender, + Configurations* base) { + sender->setFromBase(base); + bool parsedSuccessfully = false; + std::stringstream ss(configurationsString); + std::string line = std::string(); + Level currLevel = Level::Unknown; + std::string currConfigStr = std::string(); + std::string currLevelStr = std::string(); + while (std::getline(ss, line)) { + parsedSuccessfully = parseLine(&line, &currConfigStr, &currLevelStr, &currLevel, sender); + ELPP_ASSERT(parsedSuccessfully, "Unable to parse configuration line: " << line); + } + return parsedSuccessfully; +} + +void Configurations::Parser::ignoreComments(std::string* line) { + std::size_t foundAt = 0; + std::size_t quotesStart = line->find("\""); + std::size_t quotesEnd = std::string::npos; + if (quotesStart != std::string::npos) { + quotesEnd = line->find("\"", quotesStart + 1); + while (quotesEnd != std::string::npos && line->at(quotesEnd - 1) == '\\') { + // Do not erase slash yet - we will erase it in parseLine(..) while loop + quotesEnd = line->find("\"", quotesEnd + 2); + } + } + if ((foundAt = line->find(base::consts::kConfigurationComment)) != std::string::npos) { + if (foundAt < quotesEnd) { + foundAt = line->find(base::consts::kConfigurationComment, quotesEnd + 1); + } + *line = line->substr(0, foundAt); + } +} + +bool Configurations::Parser::isLevel(const std::string& line) { + return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLevel)); +} + +bool Configurations::Parser::isComment(const std::string& line) { + return base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationComment)); +} + +bool Configurations::Parser::isConfig(const std::string& line) { + std::size_t assignment = line.find('='); + return line != "" && + ((line[0] >= 'A' && line[0] <= 'Z') || (line[0] >= 'a' && line[0] <= 'z')) && + (assignment != std::string::npos) && + (line.size() > assignment); +} + +bool Configurations::Parser::parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, + Level* currLevel, + Configurations* conf) { + ConfigurationType currConfig = ConfigurationType::Unknown; + std::string currValue = std::string(); + *line = base::utils::Str::trim(*line); + if (isComment(*line)) return true; + ignoreComments(line); + *line = base::utils::Str::trim(*line); + if (line->empty()) { + // Comment ignored + return true; + } + if (isLevel(*line)) { + if (line->size() <= 2) { + return true; + } + *currLevelStr = line->substr(1, line->size() - 2); + *currLevelStr = base::utils::Str::toUpper(*currLevelStr); + *currLevelStr = base::utils::Str::trim(*currLevelStr); + *currLevel = LevelHelper::convertFromString(currLevelStr->c_str()); + return true; + } + if (isConfig(*line)) { + std::size_t assignment = line->find('='); + *currConfigStr = line->substr(0, assignment); + *currConfigStr = base::utils::Str::toUpper(*currConfigStr); + *currConfigStr = base::utils::Str::trim(*currConfigStr); + currConfig = ConfigurationTypeHelper::convertFromString(currConfigStr->c_str()); + currValue = line->substr(assignment + 1); + currValue = base::utils::Str::trim(currValue); + std::size_t quotesStart = currValue.find("\"", 0); + std::size_t quotesEnd = std::string::npos; + if (quotesStart != std::string::npos) { + quotesEnd = currValue.find("\"", quotesStart + 1); + while (quotesEnd != std::string::npos && currValue.at(quotesEnd - 1) == '\\') { + currValue = currValue.erase(quotesEnd - 1, 1); + quotesEnd = currValue.find("\"", quotesEnd + 2); + } + } + if (quotesStart != std::string::npos && quotesEnd != std::string::npos) { + // Quote provided - check and strip if valid + ELPP_ASSERT((quotesStart < quotesEnd), "Configuration error - No ending quote found in [" + << currConfigStr << "]"); + ELPP_ASSERT((quotesStart + 1 != quotesEnd), "Empty configuration value for [" << currConfigStr << "]"); + if ((quotesStart != quotesEnd) && (quotesStart + 1 != quotesEnd)) { + // Explicit check in case if assertion is disabled + currValue = currValue.substr(quotesStart + 1, quotesEnd - 1); + } + } + } + ELPP_ASSERT(*currLevel != Level::Unknown, "Unrecognized severity level [" << *currLevelStr << "]"); + ELPP_ASSERT(currConfig != ConfigurationType::Unknown, "Unrecognized configuration [" << *currConfigStr << "]"); + if (*currLevel == Level::Unknown || currConfig == ConfigurationType::Unknown) { + return false; // unrecognizable level or config + } + conf->set(*currLevel, currConfig, currValue); + return true; +} + +void Configurations::unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value) { + Configuration* conf = RegistryWithPred::get(level, configurationType); + if (conf == nullptr) { + unsafeSet(level, configurationType, value); + } +} + +void Configurations::unsafeSet(Level level, ConfigurationType configurationType, const std::string& value) { + Configuration* conf = RegistryWithPred::get(level, configurationType); + if (conf == nullptr) { + registerNew(new Configuration(level, configurationType, value)); + } else { + conf->setValue(value); + } + if (level == Level::Global) { + unsafeSetGlobally(configurationType, value, false); + } +} + +void Configurations::setGlobally(ConfigurationType configurationType, const std::string& value, + bool includeGlobalLevel) { + if (includeGlobalLevel) { + set(Level::Global, configurationType, value); + } + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + set(LevelHelper::castFromInt(lIndex), configurationType, value); + return false; // Do not break lambda function yet as we need to set all levels regardless + }); +} + +void Configurations::unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, + bool includeGlobalLevel) { + if (includeGlobalLevel) { + unsafeSet(Level::Global, configurationType, value); + } + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + unsafeSet(LevelHelper::castFromInt(lIndex), configurationType, value); + return false; // Do not break lambda function yet as we need to set all levels regardless + }); +} + +// LogBuilder + +void LogBuilder::convertToColoredOutput(base::type::string_t* logLine, Level level) { + if (!m_termSupportsColor) return; + const base::type::char_t* resetColor = ELPP_LITERAL("\x1b[0m"); + if (level == Level::Error || level == Level::Fatal) + *logLine = ELPP_LITERAL("\x1b[31m") + *logLine + resetColor; + else if (level == Level::Warning) + *logLine = ELPP_LITERAL("\x1b[33m") + *logLine + resetColor; + else if (level == Level::Debug) + *logLine = ELPP_LITERAL("\x1b[32m") + *logLine + resetColor; + else if (level == Level::Info) + *logLine = ELPP_LITERAL("\x1b[36m") + *logLine + resetColor; + else if (level == Level::Trace) + *logLine = ELPP_LITERAL("\x1b[35m") + *logLine + resetColor; +} + +// Logger + +Logger::Logger(const std::string& id, base::LogStreamsReferenceMapPtr logStreamsReference) : + m_id(id), + m_typedConfigurations(nullptr), + m_parentApplicationName(std::string()), + m_isConfigured(false), + m_logStreamsReference(logStreamsReference) { + initUnflushedCount(); +} + +Logger::Logger(const std::string& id, const Configurations& configurations, + base::LogStreamsReferenceMapPtr logStreamsReference) : + m_id(id), + m_typedConfigurations(nullptr), + m_parentApplicationName(std::string()), + m_isConfigured(false), + m_logStreamsReference(logStreamsReference) { + initUnflushedCount(); + configure(configurations); +} + +Logger::Logger(const Logger& logger) { + base::utils::safeDelete(m_typedConfigurations); + m_id = logger.m_id; + m_typedConfigurations = logger.m_typedConfigurations; + m_parentApplicationName = logger.m_parentApplicationName; + m_isConfigured = logger.m_isConfigured; + m_configurations = logger.m_configurations; + m_unflushedCount = logger.m_unflushedCount; + m_logStreamsReference = logger.m_logStreamsReference; +} + +Logger& Logger::operator=(const Logger& logger) { + if (&logger != this) { + base::utils::safeDelete(m_typedConfigurations); + m_id = logger.m_id; + m_typedConfigurations = logger.m_typedConfigurations; + m_parentApplicationName = logger.m_parentApplicationName; + m_isConfigured = logger.m_isConfigured; + m_configurations = logger.m_configurations; + m_unflushedCount = logger.m_unflushedCount; + m_logStreamsReference = logger.m_logStreamsReference; + } + return *this; +} + +void Logger::configure(const Configurations& configurations) { + m_isConfigured = false; // we set it to false in case if we fail + initUnflushedCount(); + if (m_typedConfigurations != nullptr) { + Configurations* c = const_cast(m_typedConfigurations->configurations()); + if (c->hasConfiguration(Level::Global, ConfigurationType::Filename)) { + flush(); + } + } + base::threading::ScopedLock scopedLock(lock()); + if (m_configurations != configurations) { + m_configurations.setFromBase(const_cast(&configurations)); + } + base::utils::safeDelete(m_typedConfigurations); + m_typedConfigurations = new base::TypedConfigurations(&m_configurations, m_logStreamsReference); + resolveLoggerFormatSpec(); + m_isConfigured = true; +} + +void Logger::reconfigure(void) { + ELPP_INTERNAL_INFO(1, "Reconfiguring logger [" << m_id << "]"); + configure(m_configurations); +} + +bool Logger::isValidId(const std::string& id) { + for (std::string::const_iterator it = id.begin(); it != id.end(); ++it) { + if (!base::utils::Str::contains(base::consts::kValidLoggerIdSymbols, *it)) { + return false; + } + } + return true; +} + +void Logger::flush(void) { + ELPP_INTERNAL_INFO(3, "Flushing logger [" << m_id << "] all levels"); + base::threading::ScopedLock scopedLock(lock()); + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + flush(LevelHelper::castFromInt(lIndex), nullptr); + return false; + }); +} + +void Logger::flush(Level level, base::type::fstream_t* fs) { + if (fs == nullptr && m_typedConfigurations->toFile(level)) { + fs = m_typedConfigurations->fileStream(level); + } + if (fs != nullptr) { + fs->flush(); + std::unordered_map::iterator iter = m_unflushedCount.find(level); + if (iter != m_unflushedCount.end()) { + iter->second = 0; + } + Helpers::validateFileRolling(this, level); + } +} + +void Logger::initUnflushedCount(void) { + m_unflushedCount.clear(); + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + m_unflushedCount.insert(std::make_pair(LevelHelper::castFromInt(lIndex), 0)); + return false; + }); +} + +void Logger::resolveLoggerFormatSpec(void) const { + base::type::EnumType lIndex = LevelHelper::kMinValid; + LevelHelper::forEachLevel(&lIndex, [&](void) -> bool { + base::LogFormat* logFormat = + const_cast(&m_typedConfigurations->logFormat(LevelHelper::castFromInt(lIndex))); + base::utils::Str::replaceFirstWithEscape(logFormat->m_format, base::consts::kLoggerIdFormatSpecifier, m_id); + return false; + }); +} + +// el::base +namespace base { + +// el::base::utils +namespace utils { + +// File + +base::type::fstream_t* File::newFileStream(const std::string& filename) { + base::type::fstream_t *fs = new base::type::fstream_t(filename.c_str(), + base::type::fstream_t::out +#if !defined(ELPP_FRESH_LOG_FILE) + | base::type::fstream_t::app +#endif + ); +#if defined(ELPP_UNICODE) + std::locale elppUnicodeLocale(""); +# if ELPP_OS_WINDOWS + std::locale elppUnicodeLocaleWindows(elppUnicodeLocale, new std::codecvt_utf8_utf16); + elppUnicodeLocale = elppUnicodeLocaleWindows; +# endif // ELPP_OS_WINDOWS + fs->imbue(elppUnicodeLocale); +#endif // defined(ELPP_UNICODE) + if (fs->is_open()) { + fs->flush(); + } else { + base::utils::safeDelete(fs); + ELPP_INTERNAL_ERROR("Bad file [" << filename << "]", true); + } + return fs; +} + +std::size_t File::getSizeOfFile(base::type::fstream_t* fs) { + if (fs == nullptr) { + return 0; + } + // Since the file stream is appended to or truncated, the current + // offset is the file size. + std::size_t size = static_cast(fs->tellg()); + return size; +} + +bool File::pathExists(const char* path, bool considerFile) { + if (path == nullptr) { + return false; + } +#if ELPP_OS_UNIX + ELPP_UNUSED(considerFile); + struct stat st; + return (stat(path, &st) == 0); +#elif ELPP_OS_WINDOWS + DWORD fileType = GetFileAttributesA(path); + if (fileType == INVALID_FILE_ATTRIBUTES) { + return false; + } + return considerFile ? true : ((fileType & FILE_ATTRIBUTE_DIRECTORY) == 0 ? false : true); +#endif // ELPP_OS_UNIX +} + +bool File::createPath(const std::string& path) { + if (path.empty()) { + return false; + } + if (base::utils::File::pathExists(path.c_str())) { + return true; + } + int status = -1; + + char* currPath = const_cast(path.c_str()); + std::string builtPath = std::string(); +#if ELPP_OS_UNIX + if (path[0] == '/') { + builtPath = "/"; + } + currPath = STRTOK(currPath, base::consts::kFilePathSeparator, 0); +#elif ELPP_OS_WINDOWS + // Use secure functions API + char* nextTok_ = nullptr; + currPath = STRTOK(currPath, base::consts::kFilePathSeparator, &nextTok_); + ELPP_UNUSED(nextTok_); +#endif // ELPP_OS_UNIX + while (currPath != nullptr) { + builtPath.append(currPath); + builtPath.append(base::consts::kFilePathSeparator); +#if ELPP_OS_UNIX + status = mkdir(builtPath.c_str(), ELPP_LOG_PERMS); + currPath = STRTOK(nullptr, base::consts::kFilePathSeparator, 0); +#elif ELPP_OS_WINDOWS + status = _mkdir(builtPath.c_str()); + currPath = STRTOK(nullptr, base::consts::kFilePathSeparator, &nextTok_); +#endif // ELPP_OS_UNIX + } + if (status == -1) { + ELPP_INTERNAL_ERROR("Error while creating path [" << path << "]", true); + return false; + } + return true; +} + +std::string File::extractPathFromFilename(const std::string& fullPath, const char* separator) { + if ((fullPath == "") || (fullPath.find(separator) == std::string::npos)) { + return fullPath; + } + std::size_t lastSlashAt = fullPath.find_last_of(separator); + if (lastSlashAt == 0) { + return std::string(separator); + } + return fullPath.substr(0, lastSlashAt + 1); +} + +void File::buildStrippedFilename(const char* filename, char buff[], std::size_t limit) { + std::size_t sizeOfFilename = strlen(filename); + if (sizeOfFilename >= limit) { + filename += (sizeOfFilename - limit); + if (filename[0] != '.' && filename[1] != '.') { // prepend if not already + filename += 3; // 3 = '..' + STRCAT(buff, "..", limit); + } + } + STRCAT(buff, filename, limit); +} + +void File::buildBaseFilename(const std::string& fullPath, char buff[], std::size_t limit, const char* separator) { + const char *filename = fullPath.c_str(); + std::size_t lastSlashAt = fullPath.find_last_of(separator); + filename += lastSlashAt ? lastSlashAt+1 : 0; + std::size_t sizeOfFilename = strlen(filename); + if (sizeOfFilename >= limit) { + filename += (sizeOfFilename - limit); + if (filename[0] != '.' && filename[1] != '.') { // prepend if not already + filename += 3; // 3 = '..' + STRCAT(buff, "..", limit); + } + } + STRCAT(buff, filename, limit); +} + +// Str + +bool Str::wildCardMatch(const char* str, const char* pattern) { + while (*pattern) { + switch (*pattern) { + case '?': + if (!*str) + return false; + ++str; + ++pattern; + break; + case '*': + if (wildCardMatch(str, pattern + 1)) + return true; + if (*str && wildCardMatch(str + 1, pattern)) + return true; + return false; + default: + if (*str++ != *pattern++) + return false; + break; + } + } + return !*str && !*pattern; +} + +std::string& Str::ltrim(std::string& str) { + str.erase(str.begin(), std::find_if(str.begin(), str.end(), [](char c) { + return !std::isspace(c); + } )); + return str; +} + +std::string& Str::rtrim(std::string& str) { + str.erase(std::find_if(str.rbegin(), str.rend(), [](char c) { + return !std::isspace(c); + }).base(), str.end()); + return str; +} + +std::string& Str::trim(std::string& str) { + return ltrim(rtrim(str)); +} + +bool Str::startsWith(const std::string& str, const std::string& start) { + return (str.length() >= start.length()) && (str.compare(0, start.length(), start) == 0); +} + +bool Str::endsWith(const std::string& str, const std::string& end) { + return (str.length() >= end.length()) && (str.compare(str.length() - end.length(), end.length(), end) == 0); +} + +std::string& Str::replaceAll(std::string& str, char replaceWhat, char replaceWith) { + std::replace(str.begin(), str.end(), replaceWhat, replaceWith); + return str; +} + +std::string& Str::replaceAll(std::string& str, const std::string& replaceWhat, + const std::string& replaceWith) { + if (replaceWhat == replaceWith) + return str; + std::size_t foundAt = std::string::npos; + while ((foundAt = str.find(replaceWhat, foundAt + 1)) != std::string::npos) { + str.replace(foundAt, replaceWhat.length(), replaceWith); + } + return str; +} + +void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const base::type::string_t& replaceWith) { + std::size_t foundAt = base::type::string_t::npos; + while ((foundAt = str.find(replaceWhat, foundAt + 1)) != base::type::string_t::npos) { + if (foundAt > 0 && str[foundAt - 1] == base::consts::kFormatSpecifierChar) { + str.erase(foundAt - 1, 1); + ++foundAt; + } else { + str.replace(foundAt, replaceWhat.length(), replaceWith); + return; + } + } +} +#if defined(ELPP_UNICODE) +void Str::replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const std::string& replaceWith) { + replaceFirstWithEscape(str, replaceWhat, base::type::string_t(replaceWith.begin(), replaceWith.end())); +} +#endif // defined(ELPP_UNICODE) + +std::string& Str::toUpper(std::string& str) { + std::transform(str.begin(), str.end(), str.begin(), + [](char c) { + return static_cast(::toupper(c)); + }); + return str; +} + +bool Str::cStringEq(const char* s1, const char* s2) { + if (s1 == nullptr && s2 == nullptr) return true; + if (s1 == nullptr || s2 == nullptr) return false; + return strcmp(s1, s2) == 0; +} + +bool Str::cStringCaseEq(const char* s1, const char* s2) { + if (s1 == nullptr && s2 == nullptr) return true; + if (s1 == nullptr || s2 == nullptr) return false; + + // With thanks to cygwin for this code + int d = 0; + + while (true) { + const int c1 = toupper(*s1++); + const int c2 = toupper(*s2++); + + if (((d = c1 - c2) != 0) || (c2 == '\0')) { + break; + } + } + + return d == 0; +} + +bool Str::contains(const char* str, char c) { + for (; *str; ++str) { + if (*str == c) + return true; + } + return false; +} + +char* Str::convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded) { + char localBuff[10] = ""; + char* p = localBuff + sizeof(localBuff) - 2; + if (n > 0) { + for (; n > 0 && p > localBuff && len > 0; n /= 10, --len) + *--p = static_cast(n % 10 + '0'); + } else { + *--p = '0'; + --len; + } + if (zeroPadded) + while (p > localBuff && len-- > 0) *--p = static_cast('0'); + return addToBuff(p, buf, bufLim); +} + +char* Str::addToBuff(const char* str, char* buf, const char* bufLim) { + while ((buf < bufLim) && ((*buf = *str++) != '\0')) + ++buf; + return buf; +} + +char* Str::clearBuff(char buff[], std::size_t lim) { + STRCPY(buff, "", lim); + ELPP_UNUSED(lim); // For *nix we dont have anything using lim in above STRCPY macro + return buff; +} + +/// @brief Converts wchar* to char* +/// NOTE: Need to free return value after use! +char* Str::wcharPtrToCharPtr(const wchar_t* line) { + std::size_t len_ = wcslen(line) + 1; + char* buff_ = static_cast(malloc(len_ + 1)); +# if ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) + std::wcstombs(buff_, line, len_); +# elif ELPP_OS_WINDOWS + std::size_t convCount_ = 0; + mbstate_t mbState_; + ::memset(static_cast(&mbState_), 0, sizeof(mbState_)); + wcsrtombs_s(&convCount_, buff_, len_, &line, len_, &mbState_); +# endif // ELPP_OS_UNIX || (ELPP_OS_WINDOWS && !ELPP_CRT_DBG_WARNINGS) + return buff_; +} + +// OS + +#if ELPP_OS_WINDOWS +/// @brief Gets environment variables for Windows based OS. +/// We are not using getenv(const char*) because of CRT deprecation +/// @param varname Variable name to get environment variable value for +/// @return If variable exist the value of it otherwise nullptr +const char* OS::getWindowsEnvironmentVariable(const char* varname) { + const DWORD bufferLen = 50; + static char buffer[bufferLen]; + if (GetEnvironmentVariableA(varname, buffer, bufferLen)) { + return buffer; + } + return nullptr; +} +#endif // ELPP_OS_WINDOWS +#if ELPP_OS_ANDROID +std::string OS::getProperty(const char* prop) { + char propVal[PROP_VALUE_MAX + 1]; + int ret = __system_property_get(prop, propVal); + return ret == 0 ? std::string() : std::string(propVal); +} + +std::string OS::getDeviceName(void) { + std::stringstream ss; + std::string manufacturer = getProperty("ro.product.manufacturer"); + std::string model = getProperty("ro.product.model"); + if (manufacturer.empty() || model.empty()) { + return std::string(); + } + ss << manufacturer << "-" << model; + return ss.str(); +} +#endif // ELPP_OS_ANDROID + +const std::string OS::getBashOutput(const char* command) { +#if (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) + if (command == nullptr) { + return std::string(); + } + FILE* proc = nullptr; + if ((proc = popen(command, "r")) == nullptr) { + ELPP_INTERNAL_ERROR("\nUnable to run command [" << command << "]", true); + return std::string(); + } + char hBuff[4096]; + if (fgets(hBuff, sizeof(hBuff), proc) != nullptr) { + pclose(proc); + const std::size_t buffLen = strlen(hBuff); + if (buffLen > 0 && hBuff[buffLen - 1] == '\n') { + hBuff[buffLen - 1] = '\0'; + } + return std::string(hBuff); + } else { + pclose(proc); + } + return std::string(); +#else + ELPP_UNUSED(command); + return std::string(); +#endif // (ELPP_OS_UNIX && !ELPP_OS_ANDROID && !ELPP_CYGWIN) +} + +std::string OS::getEnvironmentVariable(const char* variableName, const char* defaultVal, + const char* alternativeBashCommand) { +#if ELPP_OS_UNIX + const char* val = getenv(variableName); +#elif ELPP_OS_WINDOWS + const char* val = getWindowsEnvironmentVariable(variableName); +#endif // ELPP_OS_UNIX + if ((val == nullptr) || ((strcmp(val, "") == 0))) { +#if ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) + // Try harder on unix-based systems + std::string valBash = base::utils::OS::getBashOutput(alternativeBashCommand); + if (valBash.empty()) { + return std::string(defaultVal); + } else { + return valBash; + } +#elif ELPP_OS_WINDOWS || ELPP_OS_UNIX + ELPP_UNUSED(alternativeBashCommand); + return std::string(defaultVal); +#endif // ELPP_OS_UNIX && defined(ELPP_FORCE_ENV_VAR_FROM_BASH) + } + return std::string(val); +} + +std::string OS::currentUser(void) { +#if ELPP_OS_UNIX && !ELPP_OS_ANDROID + return getEnvironmentVariable("USER", base::consts::kUnknownUser, "whoami"); +#elif ELPP_OS_WINDOWS + return getEnvironmentVariable("USERNAME", base::consts::kUnknownUser); +#elif ELPP_OS_ANDROID + ELPP_UNUSED(base::consts::kUnknownUser); + return std::string("android"); +#else + return std::string(); +#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID +} + +std::string OS::currentHost(void) { +#if ELPP_OS_UNIX && !ELPP_OS_ANDROID + return getEnvironmentVariable("HOSTNAME", base::consts::kUnknownHost, "hostname"); +#elif ELPP_OS_WINDOWS + return getEnvironmentVariable("COMPUTERNAME", base::consts::kUnknownHost); +#elif ELPP_OS_ANDROID + ELPP_UNUSED(base::consts::kUnknownHost); + return getDeviceName(); +#else + return std::string(); +#endif // ELPP_OS_UNIX && !ELPP_OS_ANDROID +} + +bool OS::termSupportsColor(void) { + std::string term = getEnvironmentVariable("TERM", ""); + return term == "xterm" || term == "xterm-color" || term == "xterm-256color" + || term == "screen" || term == "linux" || term == "cygwin" + || term == "screen-256color"; +} + +// DateTime + +void DateTime::gettimeofday(struct timeval* tv) { +#if ELPP_OS_WINDOWS + if (tv != nullptr) { +# if ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) + const unsigned __int64 delta_ = 11644473600000000Ui64; +# else + const unsigned __int64 delta_ = 11644473600000000ULL; +# endif // ELPP_COMPILER_MSVC || defined(_MSC_EXTENSIONS) + const double secOffSet = 0.000001; + const unsigned long usecOffSet = 1000000; + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + unsigned __int64 present = 0; + present |= fileTime.dwHighDateTime; + present = present << 32; + present |= fileTime.dwLowDateTime; + present /= 10; // mic-sec + // Subtract the difference + present -= delta_; + tv->tv_sec = static_cast(present * secOffSet); + tv->tv_usec = static_cast(present % usecOffSet); + } +#else + ::gettimeofday(tv, nullptr); +#endif // ELPP_OS_WINDOWS +} + +std::string DateTime::getDateTime(const char* format, const base::SubsecondPrecision* ssPrec) { + struct timeval currTime; + gettimeofday(&currTime); + return timevalToString(currTime, format, ssPrec); +} + +std::string DateTime::timevalToString(struct timeval tval, const char* format, + const el::base::SubsecondPrecision* ssPrec) { + struct ::tm timeInfo; + buildTimeInfo(&tval, &timeInfo); + const int kBuffSize = 30; + char buff_[kBuffSize] = ""; + parseFormat(buff_, kBuffSize, format, &timeInfo, static_cast(tval.tv_usec / ssPrec->m_offset), + ssPrec); + return std::string(buff_); +} + +base::type::string_t DateTime::formatTime(unsigned long long time, base::TimestampUnit timestampUnit) { + base::type::EnumType start = static_cast(timestampUnit); + const base::type::char_t* unit = base::consts::kTimeFormats[start].unit; + for (base::type::EnumType i = start; i < base::consts::kTimeFormatsCount - 1; ++i) { + if (time <= base::consts::kTimeFormats[i].value) { + break; + } + if (base::consts::kTimeFormats[i].value == 1000.0f && time / 1000.0f < 1.9f) { + break; + } + time /= static_cast(base::consts::kTimeFormats[i].value); + unit = base::consts::kTimeFormats[i + 1].unit; + } + base::type::stringstream_t ss; + ss << time << " " << unit; + return ss.str(); +} + +unsigned long long DateTime::getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, + base::TimestampUnit timestampUnit) { + if (timestampUnit == base::TimestampUnit::Microsecond) { + return static_cast(static_cast(1000000 * endTime.tv_sec + endTime.tv_usec) - + static_cast(1000000 * startTime.tv_sec + startTime.tv_usec)); + } + // milliseconds + auto conv = [](const struct timeval& tim) { + return static_cast((tim.tv_sec * 1000) + (tim.tv_usec / 1000)); + }; + return static_cast(conv(endTime) - conv(startTime)); +} + +struct ::tm* DateTime::buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo) { +#if ELPP_OS_UNIX + time_t rawTime = currTime->tv_sec; + ::elpptime_r(&rawTime, timeInfo); + return timeInfo; +#else +# if ELPP_COMPILER_MSVC + ELPP_UNUSED(currTime); + time_t t; +# if defined(_USE_32BIT_TIME_T) + _time32(&t); +# else + _time64(&t); +# endif + elpptime_s(timeInfo, &t); + return timeInfo; +# else + // For any other compilers that don't have CRT warnings issue e.g, MinGW or TDM GCC- we use different method + time_t rawTime = currTime->tv_sec; + struct tm* tmInf = elpptime(&rawTime); + *timeInfo = *tmInf; + return timeInfo; +# endif // ELPP_COMPILER_MSVC +#endif // ELPP_OS_UNIX +} + +char* DateTime::parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, + std::size_t msec, const base::SubsecondPrecision* ssPrec) { + const char* bufLim = buf + bufSz; + for (; *format; ++format) { + if (*format == base::consts::kFormatSpecifierChar) { + switch (*++format) { + case base::consts::kFormatSpecifierChar: // Escape + break; + case '\0': // End + --format; + break; + case 'd': // Day + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mday, 2, buf, bufLim); + continue; + case 'a': // Day of week (short) + buf = base::utils::Str::addToBuff(base::consts::kDaysAbbrev[tInfo->tm_wday], buf, bufLim); + continue; + case 'A': // Day of week (long) + buf = base::utils::Str::addToBuff(base::consts::kDays[tInfo->tm_wday], buf, bufLim); + continue; + case 'M': // month + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_mon + 1, 2, buf, bufLim); + continue; + case 'b': // month (short) + buf = base::utils::Str::addToBuff(base::consts::kMonthsAbbrev[tInfo->tm_mon], buf, bufLim); + continue; + case 'B': // month (long) + buf = base::utils::Str::addToBuff(base::consts::kMonths[tInfo->tm_mon], buf, bufLim); + continue; + case 'y': // year (two digits) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 2, buf, bufLim); + continue; + case 'Y': // year (four digits) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_year + base::consts::kYearBase, 4, buf, bufLim); + continue; + case 'h': // hour (12-hour) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour % 12, 2, buf, bufLim); + continue; + case 'H': // hour (24-hour) + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_hour, 2, buf, bufLim); + continue; + case 'm': // minute + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_min, 2, buf, bufLim); + continue; + case 's': // second + buf = base::utils::Str::convertAndAddToBuff(tInfo->tm_sec, 2, buf, bufLim); + continue; + case 'z': // subsecond part + case 'g': + buf = base::utils::Str::convertAndAddToBuff(msec, ssPrec->m_width, buf, bufLim); + continue; + case 'F': // AM/PM + buf = base::utils::Str::addToBuff((tInfo->tm_hour >= 12) ? base::consts::kPm : base::consts::kAm, buf, bufLim); + continue; + default: + continue; + } + } + if (buf == bufLim) break; + *buf++ = *format; + } + return buf; +} + +// CommandLineArgs + +void CommandLineArgs::setArgs(int argc, char** argv) { + m_params.clear(); + m_paramsWithValue.clear(); + if (argc == 0 || argv == nullptr) { + return; + } + m_argc = argc; + m_argv = argv; + for (int i = 1; i < m_argc; ++i) { + const char* v = (strstr(m_argv[i], "=")); + if (v != nullptr && strlen(v) > 0) { + std::string key = std::string(m_argv[i]); + key = key.substr(0, key.find_first_of('=')); + if (hasParamWithValue(key.c_str())) { + ELPP_INTERNAL_INFO(1, "Skipping [" << key << "] arg since it already has value [" + << getParamValue(key.c_str()) << "]"); + } else { + m_paramsWithValue.insert(std::make_pair(key, std::string(v + 1))); + } + } + if (v == nullptr) { + if (hasParam(m_argv[i])) { + ELPP_INTERNAL_INFO(1, "Skipping [" << m_argv[i] << "] arg since it already exists"); + } else { + m_params.push_back(std::string(m_argv[i])); + } + } + } +} + +bool CommandLineArgs::hasParamWithValue(const char* paramKey) const { + return m_paramsWithValue.find(std::string(paramKey)) != m_paramsWithValue.end(); +} + +const char* CommandLineArgs::getParamValue(const char* paramKey) const { + std::unordered_map::const_iterator iter = m_paramsWithValue.find(std::string(paramKey)); + return iter != m_paramsWithValue.end() ? iter->second.c_str() : ""; +} + +bool CommandLineArgs::hasParam(const char* paramKey) const { + return std::find(m_params.begin(), m_params.end(), std::string(paramKey)) != m_params.end(); +} + +bool CommandLineArgs::empty(void) const { + return m_params.empty() && m_paramsWithValue.empty(); +} + +std::size_t CommandLineArgs::size(void) const { + return m_params.size() + m_paramsWithValue.size(); +} + +base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c) { + for (int i = 1; i < c.m_argc; ++i) { + os << ELPP_LITERAL("[") << c.m_argv[i] << ELPP_LITERAL("]"); + if (i < c.m_argc - 1) { + os << ELPP_LITERAL(" "); + } + } + return os; +} + +} // namespace utils + +// el::base::threading +namespace threading { + +#if ELPP_THREADING_ENABLED +# if ELPP_USE_STD_THREADING +# if ELPP_ASYNC_LOGGING +static void msleep(int ms) { + // Only when async logging enabled - this is because async is strict on compiler +# if defined(ELPP_NO_SLEEP_FOR) + usleep(ms * 1000); +# else + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +# endif // defined(ELPP_NO_SLEEP_FOR) +} +# endif // ELPP_ASYNC_LOGGING +# endif // !ELPP_USE_STD_THREADING +#endif // ELPP_THREADING_ENABLED + +} // namespace threading + +// el::base + +// SubsecondPrecision + +void SubsecondPrecision::init(int width) { + if (width < 1 || width > 6) { + width = base::consts::kDefaultSubsecondPrecision; + } + m_width = width; + switch (m_width) { + case 3: + m_offset = 1000; + break; + case 4: + m_offset = 100; + break; + case 5: + m_offset = 10; + break; + case 6: + m_offset = 1; + break; + default: + m_offset = 1000; + break; + } +} + +// LogFormat + +LogFormat::LogFormat(void) : + m_level(Level::Unknown), + m_userFormat(base::type::string_t()), + m_format(base::type::string_t()), + m_dateTimeFormat(std::string()), + m_flags(0x0), + m_currentUser(base::utils::OS::currentUser()), + m_currentHost(base::utils::OS::currentHost()) { +} + +LogFormat::LogFormat(Level level, const base::type::string_t& format) + : m_level(level), m_userFormat(format), m_currentUser(base::utils::OS::currentUser()), + m_currentHost(base::utils::OS::currentHost()) { + parseFromFormat(m_userFormat); +} + +LogFormat::LogFormat(const LogFormat& logFormat): + m_level(logFormat.m_level), + m_userFormat(logFormat.m_userFormat), + m_format(logFormat.m_format), + m_dateTimeFormat(logFormat.m_dateTimeFormat), + m_flags(logFormat.m_flags), + m_currentUser(logFormat.m_currentUser), + m_currentHost(logFormat.m_currentHost) { +} + +LogFormat::LogFormat(LogFormat&& logFormat) { + m_level = std::move(logFormat.m_level); + m_userFormat = std::move(logFormat.m_userFormat); + m_format = std::move(logFormat.m_format); + m_dateTimeFormat = std::move(logFormat.m_dateTimeFormat); + m_flags = std::move(logFormat.m_flags); + m_currentUser = std::move(logFormat.m_currentUser); + m_currentHost = std::move(logFormat.m_currentHost); +} + +LogFormat& LogFormat::operator=(const LogFormat& logFormat) { + if (&logFormat != this) { + m_level = logFormat.m_level; + m_userFormat = logFormat.m_userFormat; + m_dateTimeFormat = logFormat.m_dateTimeFormat; + m_flags = logFormat.m_flags; + m_currentUser = logFormat.m_currentUser; + m_currentHost = logFormat.m_currentHost; + } + return *this; +} + +bool LogFormat::operator==(const LogFormat& other) { + return m_level == other.m_level && m_userFormat == other.m_userFormat && m_format == other.m_format && + m_dateTimeFormat == other.m_dateTimeFormat && m_flags == other.m_flags; +} + +/// @brief Updates format to be used while logging. +/// @param userFormat User provided format +void LogFormat::parseFromFormat(const base::type::string_t& userFormat) { + // We make copy because we will be changing the format + // i.e, removing user provided date format from original format + // and then storing it. + base::type::string_t formatCopy = userFormat; + m_flags = 0x0; + auto conditionalAddFlag = [&](const base::type::char_t* specifier, base::FormatFlags flag) { + std::size_t foundAt = base::type::string_t::npos; + while ((foundAt = formatCopy.find(specifier, foundAt + 1)) != base::type::string_t::npos) { + if (foundAt > 0 && formatCopy[foundAt - 1] == base::consts::kFormatSpecifierChar) { + if (hasFlag(flag)) { + // If we already have flag we remove the escape chars so that '%%' is turned to '%' + // even after specifier resolution - this is because we only replaceFirst specifier + formatCopy.erase(foundAt - 1, 1); + ++foundAt; + } + } else { + if (!hasFlag(flag)) addFlag(flag); + } + } + }; + conditionalAddFlag(base::consts::kAppNameFormatSpecifier, base::FormatFlags::AppName); + conditionalAddFlag(base::consts::kSeverityLevelFormatSpecifier, base::FormatFlags::Level); + conditionalAddFlag(base::consts::kSeverityLevelShortFormatSpecifier, base::FormatFlags::LevelShort); + conditionalAddFlag(base::consts::kLoggerIdFormatSpecifier, base::FormatFlags::LoggerId); + conditionalAddFlag(base::consts::kThreadIdFormatSpecifier, base::FormatFlags::ThreadId); + conditionalAddFlag(base::consts::kLogFileFormatSpecifier, base::FormatFlags::File); + conditionalAddFlag(base::consts::kLogFileBaseFormatSpecifier, base::FormatFlags::FileBase); + conditionalAddFlag(base::consts::kLogLineFormatSpecifier, base::FormatFlags::Line); + conditionalAddFlag(base::consts::kLogLocationFormatSpecifier, base::FormatFlags::Location); + conditionalAddFlag(base::consts::kLogFunctionFormatSpecifier, base::FormatFlags::Function); + conditionalAddFlag(base::consts::kCurrentUserFormatSpecifier, base::FormatFlags::User); + conditionalAddFlag(base::consts::kCurrentHostFormatSpecifier, base::FormatFlags::Host); + conditionalAddFlag(base::consts::kMessageFormatSpecifier, base::FormatFlags::LogMessage); + conditionalAddFlag(base::consts::kVerboseLevelFormatSpecifier, base::FormatFlags::VerboseLevel); + // For date/time we need to extract user's date format first + std::size_t dateIndex = std::string::npos; + if ((dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier)) != std::string::npos) { + while (dateIndex != std::string::npos && dateIndex > 0 && formatCopy[dateIndex - 1] == base::consts::kFormatSpecifierChar) { + dateIndex = formatCopy.find(base::consts::kDateTimeFormatSpecifier, dateIndex + 1); + } + if (dateIndex != std::string::npos) { + addFlag(base::FormatFlags::DateTime); + updateDateFormat(dateIndex, formatCopy); + } + } + m_format = formatCopy; + updateFormatSpec(); +} + +void LogFormat::updateDateFormat(std::size_t index, base::type::string_t& currFormat) { + if (hasFlag(base::FormatFlags::DateTime)) { + index += ELPP_STRLEN(base::consts::kDateTimeFormatSpecifier); + } + const base::type::char_t* ptr = currFormat.c_str() + index; + if ((currFormat.size() > index) && (ptr[0] == '{')) { + // User has provided format for date/time + ++ptr; + int count = 1; // Start by 1 in order to remove starting brace + std::stringstream ss; + for (; *ptr; ++ptr, ++count) { + if (*ptr == '}') { + ++count; // In order to remove ending brace + break; + } + ss << static_cast(*ptr); + } + currFormat.erase(index, count); + m_dateTimeFormat = ss.str(); + } else { + // No format provided, use default + if (hasFlag(base::FormatFlags::DateTime)) { + m_dateTimeFormat = std::string(base::consts::kDefaultDateTimeFormat); + } + } +} + +void LogFormat::updateFormatSpec(void) { + // Do not use switch over strongly typed enums because Intel C++ compilers dont support them yet. + if (m_level == Level::Debug) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kDebugLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kDebugLevelShortLogValue); + } else if (m_level == Level::Info) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kInfoLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kInfoLevelShortLogValue); + } else if (m_level == Level::Warning) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kWarningLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kWarningLevelShortLogValue); + } else if (m_level == Level::Error) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kErrorLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kErrorLevelShortLogValue); + } else if (m_level == Level::Fatal) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kFatalLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kFatalLevelShortLogValue); + } else if (m_level == Level::Verbose) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kVerboseLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kVerboseLevelShortLogValue); + } else if (m_level == Level::Trace) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelFormatSpecifier, + base::consts::kTraceLevelLogValue); + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kSeverityLevelShortFormatSpecifier, + base::consts::kTraceLevelShortLogValue); + } + if (hasFlag(base::FormatFlags::User)) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentUserFormatSpecifier, + m_currentUser); + } + if (hasFlag(base::FormatFlags::Host)) { + base::utils::Str::replaceFirstWithEscape(m_format, base::consts::kCurrentHostFormatSpecifier, + m_currentHost); + } + // Ignore Level::Global and Level::Unknown +} + +// TypedConfigurations + +TypedConfigurations::TypedConfigurations(Configurations* configurations, + LogStreamsReferenceMapPtr logStreamsReference) { + m_configurations = configurations; + m_logStreamsReference = logStreamsReference; + build(m_configurations); +} + +TypedConfigurations::TypedConfigurations(const TypedConfigurations& other) { + this->m_configurations = other.m_configurations; + this->m_logStreamsReference = other.m_logStreamsReference; + build(m_configurations); +} + +bool TypedConfigurations::enabled(Level level) { + return getConfigByVal(level, &m_enabledMap, "enabled"); +} + +bool TypedConfigurations::toFile(Level level) { + return getConfigByVal(level, &m_toFileMap, "toFile"); +} + +const std::string& TypedConfigurations::filename(Level level) { + return getConfigByRef(level, &m_filenameMap, "filename"); +} + +bool TypedConfigurations::toStandardOutput(Level level) { + return getConfigByVal(level, &m_toStandardOutputMap, "toStandardOutput"); +} + +const base::LogFormat& TypedConfigurations::logFormat(Level level) { + return getConfigByRef(level, &m_logFormatMap, "logFormat"); +} + +const base::SubsecondPrecision& TypedConfigurations::subsecondPrecision(Level level) { + return getConfigByRef(level, &m_subsecondPrecisionMap, "subsecondPrecision"); +} + +const base::MillisecondsWidth& TypedConfigurations::millisecondsWidth(Level level) { + return getConfigByRef(level, &m_subsecondPrecisionMap, "millisecondsWidth"); +} + +bool TypedConfigurations::performanceTracking(Level level) { + return getConfigByVal(level, &m_performanceTrackingMap, "performanceTracking"); +} + +base::type::fstream_t* TypedConfigurations::fileStream(Level level) { + return getConfigByRef(level, &m_fileStreamMap, "fileStream").get(); +} + +std::size_t TypedConfigurations::maxLogFileSize(Level level) { + return getConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); +} + +std::size_t TypedConfigurations::logFlushThreshold(Level level) { + return getConfigByVal(level, &m_logFlushThresholdMap, "logFlushThreshold"); +} + +void TypedConfigurations::build(Configurations* configurations) { + base::threading::ScopedLock scopedLock(lock()); + auto getBool = [] (std::string boolStr) -> bool { // Pass by value for trimming + base::utils::Str::trim(boolStr); + return (boolStr == "TRUE" || boolStr == "true" || boolStr == "1"); + }; + std::vector withFileSizeLimit; + for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { + Configuration* conf = *it; + // We cannot use switch on strong enums because Intel C++ dont support them yet + if (conf->configurationType() == ConfigurationType::Enabled) { + setValue(conf->level(), getBool(conf->value()), &m_enabledMap); + } else if (conf->configurationType() == ConfigurationType::ToFile) { + setValue(conf->level(), getBool(conf->value()), &m_toFileMap); + } else if (conf->configurationType() == ConfigurationType::ToStandardOutput) { + setValue(conf->level(), getBool(conf->value()), &m_toStandardOutputMap); + } else if (conf->configurationType() == ConfigurationType::Filename) { + // We do not yet configure filename but we will configure in another + // loop. This is because if file cannot be created, we will force ToFile + // to be false. Because configuring logger is not necessarily performance + // sensitive operation, we can live with another loop; (by the way this loop + // is not very heavy either) + } else if (conf->configurationType() == ConfigurationType::Format) { + setValue(conf->level(), base::LogFormat(conf->level(), + base::type::string_t(conf->value().begin(), conf->value().end())), &m_logFormatMap); + } else if (conf->configurationType() == ConfigurationType::SubsecondPrecision) { + setValue(Level::Global, + base::SubsecondPrecision(static_cast(getULong(conf->value()))), &m_subsecondPrecisionMap); + } else if (conf->configurationType() == ConfigurationType::PerformanceTracking) { + setValue(Level::Global, getBool(conf->value()), &m_performanceTrackingMap); + } else if (conf->configurationType() == ConfigurationType::MaxLogFileSize) { + auto v = getULong(conf->value()); + setValue(conf->level(), static_cast(v), &m_maxLogFileSizeMap); + if (v != 0) { + withFileSizeLimit.push_back(conf); + } + } else if (conf->configurationType() == ConfigurationType::LogFlushThreshold) { + setValue(conf->level(), static_cast(getULong(conf->value())), &m_logFlushThresholdMap); + } + } + // As mentioned earlier, we will now set filename configuration in separate loop to deal with non-existent files + for (Configurations::const_iterator it = configurations->begin(); it != configurations->end(); ++it) { + Configuration* conf = *it; + if (conf->configurationType() == ConfigurationType::Filename) { + insertFile(conf->level(), conf->value()); + } + } + for (std::vector::iterator conf = withFileSizeLimit.begin(); + conf != withFileSizeLimit.end(); ++conf) { + // This is not unsafe as mutex is locked in currect scope + unsafeValidateFileRolling((*conf)->level(), base::defaultPreRollOutCallback); + } +} + +unsigned long TypedConfigurations::getULong(std::string confVal) { + bool valid = true; + base::utils::Str::trim(confVal); + valid = !confVal.empty() && std::find_if(confVal.begin(), confVal.end(), + [](char c) { + return !base::utils::Str::isDigit(c); + }) == confVal.end(); + if (!valid) { + valid = false; + ELPP_ASSERT(valid, "Configuration value not a valid integer [" << confVal << "]"); + return 0; + } + return atol(confVal.c_str()); +} + +std::string TypedConfigurations::resolveFilename(const std::string& filename) { + std::string resultingFilename = filename; + std::size_t dateIndex = std::string::npos; + std::string dateTimeFormatSpecifierStr = std::string(base::consts::kDateTimeFormatSpecifierForFilename); + if ((dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str())) != std::string::npos) { + while (dateIndex > 0 && resultingFilename[dateIndex - 1] == base::consts::kFormatSpecifierChar) { + dateIndex = resultingFilename.find(dateTimeFormatSpecifierStr.c_str(), dateIndex + 1); + } + if (dateIndex != std::string::npos) { + const char* ptr = resultingFilename.c_str() + dateIndex; + // Goto end of specifier + ptr += dateTimeFormatSpecifierStr.size(); + std::string fmt; + if ((resultingFilename.size() > dateIndex) && (ptr[0] == '{')) { + // User has provided format for date/time + ++ptr; + int count = 1; // Start by 1 in order to remove starting brace + std::stringstream ss; + for (; *ptr; ++ptr, ++count) { + if (*ptr == '}') { + ++count; // In order to remove ending brace + break; + } + ss << *ptr; + } + resultingFilename.erase(dateIndex + dateTimeFormatSpecifierStr.size(), count); + fmt = ss.str(); + } else { + fmt = std::string(base::consts::kDefaultDateTimeFormatInFilename); + } + base::SubsecondPrecision ssPrec(3); + std::string now = base::utils::DateTime::getDateTime(fmt.c_str(), &ssPrec); + base::utils::Str::replaceAll(now, '/', '-'); // Replace path element since we are dealing with filename + base::utils::Str::replaceAll(resultingFilename, dateTimeFormatSpecifierStr, now); + } + } + return resultingFilename; +} + +void TypedConfigurations::insertFile(Level level, const std::string& fullFilename) { + std::string resolvedFilename = resolveFilename(fullFilename); + if (resolvedFilename.empty()) { + std::cerr << "Could not load empty file for logging, please re-check your configurations for level [" + << LevelHelper::convertToString(level) << "]"; + } + std::string filePath = base::utils::File::extractPathFromFilename(resolvedFilename, base::consts::kFilePathSeparator); + if (filePath.size() < resolvedFilename.size()) { + base::utils::File::createPath(filePath); + } + auto create = [&](Level level) { + base::LogStreamsReferenceMap::iterator filestreamIter = m_logStreamsReference->find(resolvedFilename); + base::type::fstream_t* fs = nullptr; + if (filestreamIter == m_logStreamsReference->end()) { + // We need a completely new stream, nothing to share with + fs = base::utils::File::newFileStream(resolvedFilename); + m_filenameMap.insert(std::make_pair(level, resolvedFilename)); + m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(fs))); + m_logStreamsReference->insert(std::make_pair(resolvedFilename, base::FileStreamPtr(m_fileStreamMap.at(level)))); + } else { + // Woops! we have an existing one, share it! + m_filenameMap.insert(std::make_pair(level, filestreamIter->first)); + m_fileStreamMap.insert(std::make_pair(level, base::FileStreamPtr(filestreamIter->second))); + fs = filestreamIter->second.get(); + } + if (fs == nullptr) { + // We display bad file error from newFileStream() + ELPP_INTERNAL_ERROR("Setting [TO_FILE] of [" + << LevelHelper::convertToString(level) << "] to FALSE", false); + setValue(level, false, &m_toFileMap); + } + }; + // If we dont have file conf for any level, create it for Level::Global first + // otherwise create for specified level + create(m_filenameMap.empty() && m_fileStreamMap.empty() ? Level::Global : level); +} + +bool TypedConfigurations::unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { + base::type::fstream_t* fs = unsafeGetConfigByRef(level, &m_fileStreamMap, "fileStream").get(); + if (fs == nullptr) { + return true; + } + std::size_t maxLogFileSize = unsafeGetConfigByVal(level, &m_maxLogFileSizeMap, "maxLogFileSize"); + std::size_t currFileSize = base::utils::File::getSizeOfFile(fs); + if (maxLogFileSize != 0 && currFileSize >= maxLogFileSize) { + std::string fname = unsafeGetConfigByRef(level, &m_filenameMap, "filename"); + ELPP_INTERNAL_INFO(1, "Truncating log file [" << fname << "] as a result of configurations for level [" + << LevelHelper::convertToString(level) << "]"); + fs->close(); + preRollOutCallback(fname.c_str(), currFileSize); + fs->open(fname, std::fstream::out | std::fstream::trunc); + return true; + } + return false; +} + +// RegisteredHitCounters + +bool RegisteredHitCounters::validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + counter->validateHitCounts(n); + bool result = (n >= 1 && counter->hitCounts() != 0 && counter->hitCounts() % n == 0); + return result; +} + +/// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one +/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned +bool RegisteredHitCounters::validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + // Do not use validateHitCounts here since we do not want to reset counter here + // Note the >= instead of > because we are incrementing + // after this check + if (counter->hitCounts() >= n) + return true; + counter->increment(); + return false; +} + +/// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one +/// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned +bool RegisteredHitCounters::validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + base::threading::ScopedLock scopedLock(lock()); + base::HitCounter* counter = get(filename, lineNumber); + if (counter == nullptr) { + registerNew(counter = new base::HitCounter(filename, lineNumber)); + } + counter->increment(); + // Do not use validateHitCounts here since we do not want to reset counter here + if (counter->hitCounts() <= n) + return true; + return false; +} + +// RegisteredLoggers + +RegisteredLoggers::RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder) : + m_defaultLogBuilder(defaultLogBuilder) { + m_defaultConfigurations.setToDefault(); + m_logStreamsReference = std::make_shared(); +} + +Logger* RegisteredLoggers::get(const std::string& id, bool forceCreation) { + base::threading::ScopedLock scopedLock(lock()); + Logger* logger_ = base::utils::Registry::get(id); + if (logger_ == nullptr && forceCreation) { + bool validId = Logger::isValidId(id); + if (!validId) { + ELPP_ASSERT(validId, "Invalid logger ID [" << id << "]. Not registering this logger."); + return nullptr; + } + logger_ = new Logger(id, m_defaultConfigurations, m_logStreamsReference); + logger_->m_logBuilder = m_defaultLogBuilder; + registerNew(id, logger_); + LoggerRegistrationCallback* callback = nullptr; + for (const std::pair& h + : m_loggerRegistrationCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(logger_); + } + } + } + return logger_; +} + +bool RegisteredLoggers::remove(const std::string& id) { + if (id == base::consts::kDefaultLoggerId) { + return false; + } + // get has internal lock + Logger* logger = base::utils::Registry::get(id); + if (logger != nullptr) { + // unregister has internal lock + unregister(logger); + } + return true; +} + +void RegisteredLoggers::unsafeFlushAll(void) { + ELPP_INTERNAL_INFO(1, "Flushing all log files"); + for (base::LogStreamsReferenceMap::iterator it = m_logStreamsReference->begin(); + it != m_logStreamsReference->end(); ++it) { + if (it->second.get() == nullptr) continue; + it->second->flush(); + } +} + +// VRegistry + +VRegistry::VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags) : m_level(level), m_pFlags(pFlags) { +} + +/// @brief Sets verbose level. Accepted range is 0-9 +void VRegistry::setLevel(base::type::VerboseLevel level) { + base::threading::ScopedLock scopedLock(lock()); + if (level > 9) + m_level = base::consts::kMaxVerboseLevel; + else + m_level = level; +} + +void VRegistry::setModules(const char* modules) { + base::threading::ScopedLock scopedLock(lock()); + auto addSuffix = [](std::stringstream& ss, const char* sfx, const char* prev) { + if (prev != nullptr && base::utils::Str::endsWith(ss.str(), std::string(prev))) { + std::string chr(ss.str().substr(0, ss.str().size() - strlen(prev))); + ss.str(std::string("")); + ss << chr; + } + if (base::utils::Str::endsWith(ss.str(), std::string(sfx))) { + std::string chr(ss.str().substr(0, ss.str().size() - strlen(sfx))); + ss.str(std::string("")); + ss << chr; + } + ss << sfx; + }; + auto insert = [&](std::stringstream& ss, base::type::VerboseLevel level) { + if (!base::utils::hasFlag(LoggingFlag::DisableVModulesExtensions, *m_pFlags)) { + addSuffix(ss, ".h", nullptr); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".c", ".h"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cpp", ".c"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cc", ".cpp"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".cxx", ".cc"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".-inl.h", ".cxx"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hxx", ".-inl.h"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hpp", ".hxx"); + m_modules.insert(std::make_pair(ss.str(), level)); + addSuffix(ss, ".hh", ".hpp"); + } + m_modules.insert(std::make_pair(ss.str(), level)); + }; + bool isMod = true; + bool isLevel = false; + std::stringstream ss; + int level = -1; + for (; *modules; ++modules) { + switch (*modules) { + case '=': + isLevel = true; + isMod = false; + break; + case ',': + isLevel = false; + isMod = true; + if (!ss.str().empty() && level != -1) { + insert(ss, static_cast(level)); + ss.str(std::string("")); + level = -1; + } + break; + default: + if (isMod) { + ss << *modules; + } else if (isLevel) { + if (isdigit(*modules)) { + level = static_cast(*modules) - 48; + } + } + break; + } + } + if (!ss.str().empty() && level != -1) { + insert(ss, static_cast(level)); + } +} + +bool VRegistry::allowed(base::type::VerboseLevel vlevel, const char* file) { + base::threading::ScopedLock scopedLock(lock()); + if (m_modules.empty() || file == nullptr) { + return vlevel <= m_level; + } else { + char baseFilename[base::consts::kSourceFilenameMaxLength] = ""; + base::utils::File::buildBaseFilename(file, baseFilename); + std::unordered_map::iterator it = m_modules.begin(); + for (; it != m_modules.end(); ++it) { + if (base::utils::Str::wildCardMatch(baseFilename, it->first.c_str())) { + return vlevel <= it->second; + } + } + if (base::utils::hasFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified, *m_pFlags)) { + return true; + } + return false; + } +} + +void VRegistry::setFromArgs(const base::utils::CommandLineArgs* commandLineArgs) { + if (commandLineArgs->hasParam("-v") || commandLineArgs->hasParam("--verbose") || + commandLineArgs->hasParam("-V") || commandLineArgs->hasParam("--VERBOSE")) { + setLevel(base::consts::kMaxVerboseLevel); + } else if (commandLineArgs->hasParamWithValue("--v")) { + setLevel(static_cast(atoi(commandLineArgs->getParamValue("--v")))); + } else if (commandLineArgs->hasParamWithValue("--V")) { + setLevel(static_cast(atoi(commandLineArgs->getParamValue("--V")))); + } else if ((commandLineArgs->hasParamWithValue("-vmodule")) && vModulesEnabled()) { + setModules(commandLineArgs->getParamValue("-vmodule")); + } else if (commandLineArgs->hasParamWithValue("-VMODULE") && vModulesEnabled()) { + setModules(commandLineArgs->getParamValue("-VMODULE")); + } +} + +#if !defined(ELPP_DEFAULT_LOGGING_FLAGS) +# define ELPP_DEFAULT_LOGGING_FLAGS 0x0 +#endif // !defined(ELPP_DEFAULT_LOGGING_FLAGS) +// Storage +#if ELPP_ASYNC_LOGGING +Storage::Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker) : +#else +Storage::Storage(const LogBuilderPtr& defaultLogBuilder) : +#endif // ELPP_ASYNC_LOGGING + m_registeredHitCounters(new base::RegisteredHitCounters()), + m_registeredLoggers(new base::RegisteredLoggers(defaultLogBuilder)), + m_flags(ELPP_DEFAULT_LOGGING_FLAGS), + m_vRegistry(new base::VRegistry(0, &m_flags)), + +#if ELPP_ASYNC_LOGGING + m_asyncLogQueue(new base::AsyncLogQueue()), + m_asyncDispatchWorker(asyncDispatchWorker), +#endif // ELPP_ASYNC_LOGGING + + m_preRollOutCallback(base::defaultPreRollOutCallback) { + // Register default logger + m_registeredLoggers->get(std::string(base::consts::kDefaultLoggerId)); + // We register default logger anyway (worse case it's not going to register) just in case + m_registeredLoggers->get("default"); + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + // Register performance logger and reconfigure format + Logger* performanceLogger = m_registeredLoggers->get(std::string(base::consts::kPerformanceLoggerId)); + m_registeredLoggers->get("performance"); + performanceLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%datetime %level %msg")); + performanceLogger->reconfigure(); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +#if defined(ELPP_SYSLOG) + // Register syslog logger and reconfigure format + Logger* sysLogLogger = m_registeredLoggers->get(std::string(base::consts::kSysLogLoggerId)); + sysLogLogger->configurations()->setGlobally(ConfigurationType::Format, std::string("%level: %msg")); + sysLogLogger->reconfigure(); +#endif // defined(ELPP_SYSLOG) + addFlag(LoggingFlag::AllowVerboseIfModuleNotSpecified); +#if ELPP_ASYNC_LOGGING + installLogDispatchCallback(std::string("AsyncLogDispatchCallback")); +#else + installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); +#endif // ELPP_ASYNC_LOGGING +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + installPerformanceTrackingCallback + (std::string("DefaultPerformanceTrackingCallback")); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + ELPP_INTERNAL_INFO(1, "Easylogging++ has been initialized"); +#if ELPP_ASYNC_LOGGING + m_asyncDispatchWorker->start(); +#endif // ELPP_ASYNC_LOGGING +} + +Storage::~Storage(void) { + ELPP_INTERNAL_INFO(4, "Destroying storage"); +#if ELPP_ASYNC_LOGGING + ELPP_INTERNAL_INFO(5, "Replacing log dispatch callback to synchronous"); + uninstallLogDispatchCallback(std::string("AsyncLogDispatchCallback")); + installLogDispatchCallback(std::string("DefaultLogDispatchCallback")); + ELPP_INTERNAL_INFO(5, "Destroying asyncDispatchWorker"); + base::utils::safeDelete(m_asyncDispatchWorker); + ELPP_INTERNAL_INFO(5, "Destroying asyncLogQueue"); + base::utils::safeDelete(m_asyncLogQueue); +#endif // ELPP_ASYNC_LOGGING + ELPP_INTERNAL_INFO(5, "Destroying registeredHitCounters"); + base::utils::safeDelete(m_registeredHitCounters); + ELPP_INTERNAL_INFO(5, "Destroying registeredLoggers"); + base::utils::safeDelete(m_registeredLoggers); + ELPP_INTERNAL_INFO(5, "Destroying vRegistry"); + base::utils::safeDelete(m_vRegistry); +} + +bool Storage::hasCustomFormatSpecifier(const char* formatSpecifier) { + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + return std::find(m_customFormatSpecifiers.begin(), m_customFormatSpecifiers.end(), + formatSpecifier) != m_customFormatSpecifiers.end(); +} + +void Storage::installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { + if (hasCustomFormatSpecifier(customFormatSpecifier.formatSpecifier())) { + return; + } + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + m_customFormatSpecifiers.push_back(customFormatSpecifier); +} + +bool Storage::uninstallCustomFormatSpecifier(const char* formatSpecifier) { + base::threading::ScopedLock scopedLock(customFormatSpecifiersLock()); + std::vector::iterator it = std::find(m_customFormatSpecifiers.begin(), + m_customFormatSpecifiers.end(), formatSpecifier); + if (it != m_customFormatSpecifiers.end() && strcmp(formatSpecifier, it->formatSpecifier()) == 0) { + m_customFormatSpecifiers.erase(it); + return true; + } + return false; +} + +void Storage::setApplicationArguments(int argc, char** argv) { + m_commandLineArgs.setArgs(argc, argv); + m_vRegistry->setFromArgs(commandLineArgs()); + // default log file +#if !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) + if (m_commandLineArgs.hasParamWithValue(base::consts::kDefaultLogFileParam)) { + Configurations c; + c.setGlobally(ConfigurationType::Filename, + std::string(m_commandLineArgs.getParamValue(base::consts::kDefaultLogFileParam))); + registeredLoggers()->setDefaultConfigurations(c); + for (base::RegisteredLoggers::iterator it = registeredLoggers()->begin(); + it != registeredLoggers()->end(); ++it) { + it->second->configure(c); + } + } +#endif // !defined(ELPP_DISABLE_LOG_FILE_FROM_ARG) +#if defined(ELPP_LOGGING_FLAGS_FROM_ARG) + if (m_commandLineArgs.hasParamWithValue(base::consts::kLoggingFlagsParam)) { + int userInput = atoi(m_commandLineArgs.getParamValue(base::consts::kLoggingFlagsParam)); + if (ELPP_DEFAULT_LOGGING_FLAGS == 0x0) { + m_flags = userInput; + } else { + base::utils::addFlag(userInput, &m_flags); + } + } +#endif // defined(ELPP_LOGGING_FLAGS_FROM_ARG) +} + +} // namespace base + +// LogDispatchCallback +#if defined(ELPP_THREAD_SAFE) +void LogDispatchCallback::handle(const LogDispatchData* data) { + base::threading::ScopedLock scopedLock(m_fileLocksMapLock); + std::string filename = data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level()); + auto lock = m_fileLocks.find(filename); + if (lock == m_fileLocks.end()) { + m_fileLocks.emplace(std::make_pair(filename, std::unique_ptr(new base::threading::Mutex))); + } +} +#else +void LogDispatchCallback::handle(const LogDispatchData* /*data*/) {} +#endif + +base::threading::Mutex& LogDispatchCallback::fileHandle(const LogDispatchData* data) { + auto it = m_fileLocks.find(data->logMessage()->logger()->typedConfigurations()->filename(data->logMessage()->level())); + return *(it->second.get()); +} + +namespace base { +// DefaultLogDispatchCallback + +void DefaultLogDispatchCallback::handle(const LogDispatchData* data) { +#if defined(ELPP_THREAD_SAFE) + LogDispatchCallback::handle(data); + base::threading::ScopedLock scopedLock(fileHandle(data)); +#endif + m_data = data; + dispatch(m_data->logMessage()->logger()->logBuilder()->build(m_data->logMessage(), + m_data->dispatchAction() == base::DispatchAction::NormalLog)); +} + +void DefaultLogDispatchCallback::dispatch(base::type::string_t&& logLine) { + if (m_data->dispatchAction() == base::DispatchAction::NormalLog) { + if (m_data->logMessage()->logger()->m_typedConfigurations->toFile(m_data->logMessage()->level())) { + base::type::fstream_t* fs = m_data->logMessage()->logger()->m_typedConfigurations->fileStream( + m_data->logMessage()->level()); + if (fs != nullptr) { + fs->write(logLine.c_str(), logLine.size()); + if (fs->fail()) { + ELPP_INTERNAL_ERROR("Unable to write log to file [" + << m_data->logMessage()->logger()->m_typedConfigurations->filename(m_data->logMessage()->level()) << "].\n" + << "Few possible reasons (could be something else):\n" << " * Permission denied\n" + << " * Disk full\n" << " * Disk is not writable", true); + } else { + if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) + || (m_data->logMessage()->logger()->isFlushNeeded(m_data->logMessage()->level()))) { + m_data->logMessage()->logger()->flush(m_data->logMessage()->level(), fs); + } + } + } else { + ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(m_data->logMessage()->level()) << "] " + << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " + << m_data->logMessage()->logger()->id() << "]", false); + } + } + if (m_data->logMessage()->logger()->m_typedConfigurations->toStandardOutput(m_data->logMessage()->level())) { + if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) + m_data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, m_data->logMessage()->level()); + ELPP_COUT << ELPP_COUT_LINE(logLine); + } + } +#if defined(ELPP_SYSLOG) + else if (m_data->dispatchAction() == base::DispatchAction::SysLog) { + // Determine syslog priority + int sysLogPriority = 0; + if (m_data->logMessage()->level() == Level::Fatal) + sysLogPriority = LOG_EMERG; + else if (m_data->logMessage()->level() == Level::Error) + sysLogPriority = LOG_ERR; + else if (m_data->logMessage()->level() == Level::Warning) + sysLogPriority = LOG_WARNING; + else if (m_data->logMessage()->level() == Level::Info) + sysLogPriority = LOG_INFO; + else if (m_data->logMessage()->level() == Level::Debug) + sysLogPriority = LOG_DEBUG; + else + sysLogPriority = LOG_NOTICE; +# if defined(ELPP_UNICODE) + char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); + syslog(sysLogPriority, "%s", line); + free(line); +# else + syslog(sysLogPriority, "%s", logLine.c_str()); +# endif + } +#endif // defined(ELPP_SYSLOG) +} + +#if ELPP_ASYNC_LOGGING + +// AsyncLogDispatchCallback + +void AsyncLogDispatchCallback::handle(const LogDispatchData* data) { + base::type::string_t logLine = data->logMessage()->logger()->logBuilder()->build(data->logMessage(), + data->dispatchAction() == base::DispatchAction::NormalLog); + if (data->dispatchAction() == base::DispatchAction::NormalLog + && data->logMessage()->logger()->typedConfigurations()->toStandardOutput(data->logMessage()->level())) { + if (ELPP->hasFlag(LoggingFlag::ColoredTerminalOutput)) + data->logMessage()->logger()->logBuilder()->convertToColoredOutput(&logLine, data->logMessage()->level()); + ELPP_COUT << ELPP_COUT_LINE(logLine); + } + // Save resources and only queue if we want to write to file otherwise just ignore handler + if (data->logMessage()->logger()->typedConfigurations()->toFile(data->logMessage()->level())) { + ELPP->asyncLogQueue()->push(AsyncLogItem(*(data->logMessage()), *data, logLine)); + } +} + +// AsyncDispatchWorker +AsyncDispatchWorker::AsyncDispatchWorker() { + setContinueRunning(false); +} + +AsyncDispatchWorker::~AsyncDispatchWorker() { + setContinueRunning(false); + ELPP_INTERNAL_INFO(6, "Stopping dispatch worker - Cleaning log queue"); + clean(); + ELPP_INTERNAL_INFO(6, "Log queue cleaned"); +} + +bool AsyncDispatchWorker::clean(void) { + std::mutex m; + std::unique_lock lk(m); + cv.wait(lk, [] { return !ELPP->asyncLogQueue()->empty(); }); + emptyQueue(); + lk.unlock(); + cv.notify_one(); + return ELPP->asyncLogQueue()->empty(); +} + +void AsyncDispatchWorker::emptyQueue(void) { + while (!ELPP->asyncLogQueue()->empty()) { + AsyncLogItem data = ELPP->asyncLogQueue()->next(); + handle(&data); + base::threading::msleep(100); + } +} + +void AsyncDispatchWorker::start(void) { + base::threading::msleep(5000); // 5s (why?) + setContinueRunning(true); + std::thread t1(&AsyncDispatchWorker::run, this); + t1.join(); +} + +void AsyncDispatchWorker::handle(AsyncLogItem* logItem) { + LogDispatchData* data = logItem->data(); + LogMessage* logMessage = logItem->logMessage(); + Logger* logger = logMessage->logger(); + base::TypedConfigurations* conf = logger->typedConfigurations(); + base::type::string_t logLine = logItem->logLine(); + if (data->dispatchAction() == base::DispatchAction::NormalLog) { + if (conf->toFile(logMessage->level())) { + base::type::fstream_t* fs = conf->fileStream(logMessage->level()); + if (fs != nullptr) { + fs->write(logLine.c_str(), logLine.size()); + if (fs->fail()) { + ELPP_INTERNAL_ERROR("Unable to write log to file [" + << conf->filename(logMessage->level()) << "].\n" + << "Few possible reasons (could be something else):\n" << " * Permission denied\n" + << " * Disk full\n" << " * Disk is not writable", true); + } else { + if (ELPP->hasFlag(LoggingFlag::ImmediateFlush) || (logger->isFlushNeeded(logMessage->level()))) { + logger->flush(logMessage->level(), fs); + } + } + } else { + ELPP_INTERNAL_ERROR("Log file for [" << LevelHelper::convertToString(logMessage->level()) << "] " + << "has not been configured but [TO_FILE] is configured to TRUE. [Logger ID: " << logger->id() << "]", false); + } + } + } +# if defined(ELPP_SYSLOG) + else if (data->dispatchAction() == base::DispatchAction::SysLog) { + // Determine syslog priority + int sysLogPriority = 0; + if (logMessage->level() == Level::Fatal) + sysLogPriority = LOG_EMERG; + else if (logMessage->level() == Level::Error) + sysLogPriority = LOG_ERR; + else if (logMessage->level() == Level::Warning) + sysLogPriority = LOG_WARNING; + else if (logMessage->level() == Level::Info) + sysLogPriority = LOG_INFO; + else if (logMessage->level() == Level::Debug) + sysLogPriority = LOG_DEBUG; + else + sysLogPriority = LOG_NOTICE; +# if defined(ELPP_UNICODE) + char* line = base::utils::Str::wcharPtrToCharPtr(logLine.c_str()); + syslog(sysLogPriority, "%s", line); + free(line); +# else + syslog(sysLogPriority, "%s", logLine.c_str()); +# endif + } +# endif // defined(ELPP_SYSLOG) +} + +void AsyncDispatchWorker::run(void) { + while (continueRunning()) { + emptyQueue(); + base::threading::msleep(10); // 10ms + } +} +#endif // ELPP_ASYNC_LOGGING + +// DefaultLogBuilder + +base::type::string_t DefaultLogBuilder::build(const LogMessage* logMessage, bool appendNewLine) const { + base::TypedConfigurations* tc = logMessage->logger()->typedConfigurations(); + const base::LogFormat* logFormat = &tc->logFormat(logMessage->level()); + base::type::string_t logLine = logFormat->format(); + char buff[base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength] = ""; + const char* bufLim = buff + sizeof(buff); + if (logFormat->hasFlag(base::FormatFlags::AppName)) { + // App name + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kAppNameFormatSpecifier, + logMessage->logger()->parentApplicationName()); + } + if (logFormat->hasFlag(base::FormatFlags::ThreadId)) { + // Thread ID + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kThreadIdFormatSpecifier, + ELPP->getThreadName(base::threading::getCurrentThreadId())); + } + if (logFormat->hasFlag(base::FormatFlags::DateTime)) { + // DateTime + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kDateTimeFormatSpecifier, + base::utils::DateTime::getDateTime(logFormat->dateTimeFormat().c_str(), + &tc->subsecondPrecision(logMessage->level()))); + } + if (logFormat->hasFlag(base::FormatFlags::Function)) { + // Function + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFunctionFormatSpecifier, logMessage->func()); + } + if (logFormat->hasFlag(base::FormatFlags::File)) { + // File + base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); + base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::FileBase)) { + // FileBase + base::utils::Str::clearBuff(buff, base::consts::kSourceFilenameMaxLength); + base::utils::File::buildBaseFilename(logMessage->file(), buff); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogFileBaseFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::Line)) { + // Line + char* buf = base::utils::Str::clearBuff(buff, base::consts::kSourceLineMaxLength); + buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLineFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::Location)) { + // Location + char* buf = base::utils::Str::clearBuff(buff, + base::consts::kSourceFilenameMaxLength + base::consts::kSourceLineMaxLength); + base::utils::File::buildStrippedFilename(logMessage->file().c_str(), buff); + buf = base::utils::Str::addToBuff(buff, buf, bufLim); + buf = base::utils::Str::addToBuff(":", buf, bufLim); + buf = base::utils::Str::convertAndAddToBuff(logMessage->line(), base::consts::kSourceLineMaxLength, buf, bufLim, + false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kLogLocationFormatSpecifier, std::string(buff)); + } + if (logMessage->level() == Level::Verbose && logFormat->hasFlag(base::FormatFlags::VerboseLevel)) { + // Verbose level + char* buf = base::utils::Str::clearBuff(buff, 1); + buf = base::utils::Str::convertAndAddToBuff(logMessage->verboseLevel(), 1, buf, bufLim, false); + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kVerboseLevelFormatSpecifier, std::string(buff)); + } + if (logFormat->hasFlag(base::FormatFlags::LogMessage)) { + // Log message + base::utils::Str::replaceFirstWithEscape(logLine, base::consts::kMessageFormatSpecifier, logMessage->message()); + } +#if !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) + el::base::threading::ScopedLock lock_(ELPP->customFormatSpecifiersLock()); + ELPP_UNUSED(lock_); + for (std::vector::const_iterator it = ELPP->customFormatSpecifiers()->begin(); + it != ELPP->customFormatSpecifiers()->end(); ++it) { + std::string fs(it->formatSpecifier()); + base::type::string_t wcsFormatSpecifier(fs.begin(), fs.end()); + base::utils::Str::replaceFirstWithEscape(logLine, wcsFormatSpecifier, it->resolver()(logMessage)); + } +#endif // !defined(ELPP_DISABLE_CUSTOM_FORMAT_SPECIFIERS) + if (appendNewLine) logLine += ELPP_LITERAL("\n"); + return logLine; +} + +// LogDispatcher + +void LogDispatcher::dispatch(void) { + if (m_proceed && m_dispatchAction == base::DispatchAction::None) { + m_proceed = false; + } + if (!m_proceed) { + return; + } +#ifndef ELPP_NO_GLOBAL_LOCK + // see https://github.com/muflihun/easyloggingpp/issues/580 + // global lock is turned on by default unless + // ELPP_NO_GLOBAL_LOCK is defined + base::threading::ScopedLock scopedLock(ELPP->lock()); +#endif + base::TypedConfigurations* tc = m_logMessage->logger()->m_typedConfigurations; + if (ELPP->hasFlag(LoggingFlag::StrictLogFileSizeCheck)) { + tc->validateFileRolling(m_logMessage->level(), ELPP->preRollOutCallback()); + } + LogDispatchCallback* callback = nullptr; + LogDispatchData data; + for (const std::pair& h + : ELPP->m_logDispatchCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + data.setLogMessage(m_logMessage); + data.setDispatchAction(m_dispatchAction); + callback->handle(&data); + } + } +} + +// MessageBuilder + +void MessageBuilder::initialize(Logger* logger) { + m_logger = logger; + m_containerLogSeparator = ELPP->hasFlag(LoggingFlag::NewLineForContainer) ? + ELPP_LITERAL("\n ") : ELPP_LITERAL(", "); +} + +MessageBuilder& MessageBuilder::operator<<(const wchar_t* msg) { + if (msg == nullptr) { + m_logger->stream() << base::consts::kNullPointer; + return *this; + } +# if defined(ELPP_UNICODE) + m_logger->stream() << msg; +# else + char* buff_ = base::utils::Str::wcharPtrToCharPtr(msg); + m_logger->stream() << buff_; + free(buff_); +# endif + if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { + m_logger->stream() << " "; + } + return *this; +} + +// Writer + +Writer& Writer::construct(Logger* logger, bool needLock) { + m_logger = logger; + initializeLogger(logger->id(), false, needLock); + m_messageBuilder.initialize(m_logger); + return *this; +} + +Writer& Writer::construct(int count, const char* loggerIds, ...) { + if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { + va_list loggersList; + va_start(loggersList, loggerIds); + const char* id = loggerIds; + m_loggerIds.reserve(count); + for (int i = 0; i < count; ++i) { + m_loggerIds.push_back(std::string(id)); + id = va_arg(loggersList, const char*); + } + va_end(loggersList); + initializeLogger(m_loggerIds.at(0)); + } else { + initializeLogger(std::string(loggerIds)); + } + m_messageBuilder.initialize(m_logger); + return *this; +} + +void Writer::initializeLogger(const std::string& loggerId, bool lookup, bool needLock) { + if (lookup) { + m_logger = ELPP->registeredLoggers()->get(loggerId, ELPP->hasFlag(LoggingFlag::CreateLoggerAutomatically)); + } + if (m_logger == nullptr) { + { + if (!ELPP->registeredLoggers()->has(std::string(base::consts::kDefaultLoggerId))) { + // Somehow default logger has been unregistered. Not good! Register again + ELPP->registeredLoggers()->get(std::string(base::consts::kDefaultLoggerId)); + } + } + Writer(Level::Debug, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) + << "Logger [" << loggerId << "] is not registered yet!"; + m_proceed = false; + } else { + if (needLock) { + m_logger->acquireLock(); // This should not be unlocked by checking m_proceed because + // m_proceed can be changed by lines below + } + if (ELPP->hasFlag(LoggingFlag::HierarchicalLogging)) { + m_proceed = m_level == Level::Verbose ? m_logger->enabled(m_level) : + LevelHelper::castToInt(m_level) >= LevelHelper::castToInt(ELPP->m_loggingLevel); + } else { + m_proceed = m_logger->enabled(m_level); + } + } +} + +void Writer::processDispatch() { +#if ELPP_LOGGING_ENABLED + if (ELPP->hasFlag(LoggingFlag::MultiLoggerSupport)) { + bool firstDispatched = false; + base::type::string_t logMessage; + std::size_t i = 0; + do { + if (m_proceed) { + if (firstDispatched) { + m_logger->stream() << logMessage; + } else { + firstDispatched = true; + if (m_loggerIds.size() > 1) { + logMessage = m_logger->stream().str(); + } + } + triggerDispatch(); + } else if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + if (i + 1 < m_loggerIds.size()) { + initializeLogger(m_loggerIds.at(i + 1)); + } + } while (++i < m_loggerIds.size()); + } else { + if (m_proceed) { + triggerDispatch(); + } else if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + } +#else + if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } +#endif // ELPP_LOGGING_ENABLED +} + +void Writer::triggerDispatch(void) { + try { + if (m_proceed) { + if (m_msg == nullptr) { + LogMessage msg(m_level, m_file, m_line, m_func, m_verboseLevel, + m_logger); + base::LogDispatcher(m_proceed, &msg, m_dispatchAction).dispatch(); + } else { + base::LogDispatcher(m_proceed, m_msg, m_dispatchAction).dispatch(); + } + } + if (m_logger != nullptr) { + m_logger->stream().str(ELPP_LITERAL("")); + m_logger->releaseLock(); + } + if (m_proceed && m_level == Level::Fatal + && !ELPP->hasFlag(LoggingFlag::DisableApplicationAbortOnFatalLog)) { + base::Writer(Level::Warning, m_file, m_line, m_func).construct(1, base::consts::kDefaultLoggerId) + << "Aborting application. Reason: Fatal log at [" << m_file << ":" << m_line << "]"; + std::stringstream reasonStream; + reasonStream << "Fatal log at [" << m_file << ":" << m_line << "]" + << " If you wish to disable 'abort on fatal log' please use " + << "el::Loggers::addFlag(el::LoggingFlag::DisableApplicationAbortOnFatalLog)"; + base::utils::abort(1, reasonStream.str()); + } + m_proceed = false; + } + catch(std::exception & ex){ + // Extremely low memory situation; don't let exception be unhandled. + } +} + +// PErrorWriter + +PErrorWriter::~PErrorWriter(void) { + if (m_proceed) { +#if ELPP_COMPILER_MSVC + char buff[256]; + strerror_s(buff, 256, errno); + m_logger->stream() << ": " << buff << " [" << errno << "]"; +#else + m_logger->stream() << ": " << strerror(errno) << " [" << errno << "]"; +#endif + } +} + +// PerformanceTracker + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +PerformanceTracker::PerformanceTracker(const std::string& blockName, + base::TimestampUnit timestampUnit, + const std::string& loggerId, + bool scopedLog, Level level) : + m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog), + m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + // We store it locally so that if user happen to change configuration by the end of scope + // or before calling checkpoint, we still depend on state of configuration at time of construction + el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false); + m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level); + if (m_enabled) { + base::utils::DateTime::gettimeofday(&m_startTime); + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED +} + +PerformanceTracker::~PerformanceTracker(void) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + if (m_enabled) { + base::threading::ScopedLock scopedLock(lock()); + if (m_scopedLog) { + base::utils::DateTime::gettimeofday(&m_endTime); + base::type::string_t formattedTime = getFormattedTimeTaken(); + PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete); + data.init(this); + data.m_formattedTimeTaken = formattedTime; + PerformanceTrackingCallback* callback = nullptr; + for (const std::pair& h + : ELPP->m_performanceTrackingCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(&data); + } + } + } + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) +} + +void PerformanceTracker::checkpoint(const std::string& id, const char* file, base::type::LineNumber line, + const char* func) { +#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + if (m_enabled) { + base::threading::ScopedLock scopedLock(lock()); + base::utils::DateTime::gettimeofday(&m_endTime); + base::type::string_t formattedTime = m_hasChecked ? getFormattedTimeTaken(m_lastCheckpointTime) : ELPP_LITERAL(""); + PerformanceTrackingData data(PerformanceTrackingData::DataType::Checkpoint); + data.init(this); + data.m_checkpointId = id; + data.m_file = file; + data.m_line = line; + data.m_func = func; + data.m_formattedTimeTaken = formattedTime; + PerformanceTrackingCallback* callback = nullptr; + for (const std::pair& h + : ELPP->m_performanceTrackingCallbacks) { + callback = h.second.get(); + if (callback != nullptr && callback->enabled()) { + callback->handle(&data); + } + } + base::utils::DateTime::gettimeofday(&m_lastCheckpointTime); + m_hasChecked = true; + m_lastCheckpointId = id; + } +#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED + ELPP_UNUSED(id); + ELPP_UNUSED(file); + ELPP_UNUSED(line); + ELPP_UNUSED(func); +} + +const base::type::string_t PerformanceTracker::getFormattedTimeTaken(struct timeval startTime) const { + if (ELPP->hasFlag(LoggingFlag::FixedTimeFormat)) { + base::type::stringstream_t ss; + ss << base::utils::DateTime::getTimeDifference(m_endTime, + startTime, m_timestampUnit) << " " << base::consts::kTimeFormats[static_cast + (m_timestampUnit)].unit; + return ss.str(); + } + return base::utils::DateTime::formatTime(base::utils::DateTime::getTimeDifference(m_endTime, + startTime, m_timestampUnit), m_timestampUnit); +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + +namespace debug { +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +// StackTrace + +StackTrace::StackTraceEntry::StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, + const std::string& hex, + const std::string& addr) : + m_index(index), + m_location(loc), + m_demangled(demang), + m_hex(hex), + m_addr(addr) { +} + +std::ostream& operator<<(std::ostream& ss, const StackTrace::StackTraceEntry& si) { + ss << "[" << si.m_index << "] " << si.m_location << (si.m_hex.empty() ? "" : "+") << si.m_hex << " " << si.m_addr << + (si.m_demangled.empty() ? "" : ":") << si.m_demangled; + return ss; +} + +std::ostream& operator<<(std::ostream& os, const StackTrace& st) { + std::vector::const_iterator it = st.m_stack.begin(); + while (it != st.m_stack.end()) { + os << " " << *it++ << "\n"; + } + return os; +} + +void StackTrace::generateNew(void) { +#ifdef HAVE_EXECINFO + m_stack.clear(); + void* stack[kMaxStack]; + unsigned int size = backtrace(stack, kMaxStack); + char** strings = backtrace_symbols(stack, size); + if (size > kStackStart) { // Skip StackTrace c'tor and generateNew + for (std::size_t i = kStackStart; i < size; ++i) { + std::string mangName; + std::string location; + std::string hex; + std::string addr; + + // entry: 2 crash.cpp.bin 0x0000000101552be5 _ZN2el4base5debug10StackTraceC1Ev + 21 + const std::string line(strings[i]); + auto p = line.find("_"); + if (p != std::string::npos) { + mangName = line.substr(p); + mangName = mangName.substr(0, mangName.find(" +")); + } + p = line.find("0x"); + if (p != std::string::npos) { + addr = line.substr(p); + addr = addr.substr(0, addr.find("_")); + } + // Perform demangling if parsed properly + if (!mangName.empty()) { + int status = 0; + char* demangName = abi::__cxa_demangle(mangName.data(), 0, 0, &status); + // if demangling is successful, output the demangled function name + if (status == 0) { + // Success (see http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.3/a01696.html) + StackTraceEntry entry(i - 1, location, demangName, hex, addr); + m_stack.push_back(entry); + } else { + // Not successful - we will use mangled name + StackTraceEntry entry(i - 1, location, mangName, hex, addr); + m_stack.push_back(entry); + } + free(demangName); + } else { + StackTraceEntry entry(i - 1, line); + m_stack.push_back(entry); + } + } + } + free(strings); +#else + ELPP_INTERNAL_INFO(1, "Stacktrace generation not supported for selected compiler"); +#endif // ELPP_STACKTRACE +} + +// Static helper functions + +static std::string crashReason(int sig) { + std::stringstream ss; + bool foundReason = false; + for (int i = 0; i < base::consts::kCrashSignalsCount; ++i) { + if (base::consts::kCrashSignals[i].numb == sig) { + ss << "Application has crashed due to [" << base::consts::kCrashSignals[i].name << "] signal"; + if (ELPP->hasFlag(el::LoggingFlag::LogDetailedCrashReason)) { + ss << std::endl << + " " << base::consts::kCrashSignals[i].brief << std::endl << + " " << base::consts::kCrashSignals[i].detail; + } + foundReason = true; + } + } + if (!foundReason) { + ss << "Application has crashed due to unknown signal [" << sig << "]"; + } + return ss.str(); +} +/// @brief Logs reason of crash from sig +static void logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { + if (sig == SIGINT && ELPP->hasFlag(el::LoggingFlag::IgnoreSigInt)) { + return; + } + std::stringstream ss; + ss << "CRASH HANDLED; "; + ss << crashReason(sig); +#if ELPP_STACKTRACE + if (stackTraceIfAvailable) { + ss << std::endl << " ======= Backtrace: =========" << std::endl << base::debug::StackTrace(); + } +#else + ELPP_UNUSED(stackTraceIfAvailable); +#endif // ELPP_STACKTRACE + ELPP_WRITE_LOG(el::base::Writer, level, base::DispatchAction::NormalLog, logger) << ss.str(); +} + +static inline void crashAbort(int sig) { + base::utils::abort(sig, std::string()); +} + +/// @brief Default application crash handler +/// +/// @detail This function writes log using 'default' logger, prints stack trace for GCC based compilers and aborts program. +static inline void defaultCrashHandler(int sig) { + base::debug::logCrashReason(sig, true, Level::Fatal, base::consts::kDefaultLoggerId); + base::debug::crashAbort(sig); +} + +// CrashHandler + +CrashHandler::CrashHandler(bool useDefault) { + if (useDefault) { + setHandler(defaultCrashHandler); + } +} + +void CrashHandler::setHandler(const Handler& cHandler) { + m_handler = cHandler; +#if defined(ELPP_HANDLE_SIGABRT) + int i = 0; // SIGABRT is at base::consts::kCrashSignals[0] +#else + int i = 1; +#endif // defined(ELPP_HANDLE_SIGABRT) + for (; i < base::consts::kCrashSignalsCount; ++i) { + m_handler = signal(base::consts::kCrashSignals[i].numb, cHandler); + } +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +} // namespace debug +} // namespace base + +// el + +// Helpers + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +void Helpers::crashAbort(int sig, const char* sourceFile, unsigned int long line) { + std::stringstream ss; + ss << base::debug::crashReason(sig).c_str(); + ss << " - [Called el::Helpers::crashAbort(" << sig << ")]"; + if (sourceFile != nullptr && strlen(sourceFile) > 0) { + ss << " - Source: " << sourceFile; + if (line > 0) + ss << ":" << line; + else + ss << " (line number not specified)"; + } + base::utils::abort(sig, ss.str()); +} + +void Helpers::logCrashReason(int sig, bool stackTraceIfAvailable, Level level, const char* logger) { + el::base::debug::logCrashReason(sig, stackTraceIfAvailable, level, logger); +} + +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + +// Loggers + +Logger* Loggers::getLogger(const std::string& identity, bool registerIfNotAvailable) { + return ELPP->registeredLoggers()->get(identity, registerIfNotAvailable); +} + +void Loggers::setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr) { + ELPP->registeredLoggers()->setDefaultLogBuilder(logBuilderPtr); +} + +bool Loggers::unregisterLogger(const std::string& identity) { + return ELPP->registeredLoggers()->remove(identity); +} + +bool Loggers::hasLogger(const std::string& identity) { + return ELPP->registeredLoggers()->has(identity); +} + +Logger* Loggers::reconfigureLogger(Logger* logger, const Configurations& configurations) { + if (!logger) return nullptr; + logger->configure(configurations); + return logger; +} + +Logger* Loggers::reconfigureLogger(const std::string& identity, const Configurations& configurations) { + return Loggers::reconfigureLogger(Loggers::getLogger(identity), configurations); +} + +Logger* Loggers::reconfigureLogger(const std::string& identity, ConfigurationType configurationType, + const std::string& value) { + Logger* logger = Loggers::getLogger(identity); + if (logger == nullptr) { + return nullptr; + } + logger->configurations()->set(Level::Global, configurationType, value); + logger->reconfigure(); + return logger; +} + +void Loggers::reconfigureAllLoggers(const Configurations& configurations) { + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); + it != ELPP->registeredLoggers()->end(); ++it) { + Loggers::reconfigureLogger(it->second, configurations); + } +} + +void Loggers::reconfigureAllLoggers(Level level, ConfigurationType configurationType, + const std::string& value) { + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->begin(); + it != ELPP->registeredLoggers()->end(); ++it) { + Logger* logger = it->second; + logger->configurations()->set(level, configurationType, value); + logger->reconfigure(); + } +} + +void Loggers::setDefaultConfigurations(const Configurations& configurations, bool reconfigureExistingLoggers) { + ELPP->registeredLoggers()->setDefaultConfigurations(configurations); + if (reconfigureExistingLoggers) { + Loggers::reconfigureAllLoggers(configurations); + } +} + +const Configurations* Loggers::defaultConfigurations(void) { + return ELPP->registeredLoggers()->defaultConfigurations(); +} + +const base::LogStreamsReferenceMapPtr Loggers::logStreamsReference(void) { + return ELPP->registeredLoggers()->logStreamsReference(); +} + +base::TypedConfigurations Loggers::defaultTypedConfigurations(void) { + return base::TypedConfigurations( + ELPP->registeredLoggers()->defaultConfigurations(), + ELPP->registeredLoggers()->logStreamsReference()); +} + +std::vector* Loggers::populateAllLoggerIds(std::vector* targetList) { + targetList->clear(); + for (base::RegisteredLoggers::iterator it = ELPP->registeredLoggers()->list().begin(); + it != ELPP->registeredLoggers()->list().end(); ++it) { + targetList->push_back(it->first); + } + return targetList; +} + +void Loggers::configureFromGlobal(const char* globalConfigurationFilePath) { + std::ifstream gcfStream(globalConfigurationFilePath, std::ifstream::in); + ELPP_ASSERT(gcfStream.is_open(), "Unable to open global configuration file [" << globalConfigurationFilePath + << "] for parsing."); + std::string line = std::string(); + std::stringstream ss; + Logger* logger = nullptr; + auto configure = [&](void) { + ELPP_INTERNAL_INFO(8, "Configuring logger: '" << logger->id() << "' with configurations \n" << ss.str() + << "\n--------------"); + Configurations c; + c.parseFromText(ss.str()); + logger->configure(c); + }; + while (gcfStream.good()) { + std::getline(gcfStream, line); + ELPP_INTERNAL_INFO(1, "Parsing line: " << line); + base::utils::Str::trim(line); + if (Configurations::Parser::isComment(line)) continue; + Configurations::Parser::ignoreComments(&line); + base::utils::Str::trim(line); + if (line.size() > 2 && base::utils::Str::startsWith(line, std::string(base::consts::kConfigurationLoggerId))) { + if (!ss.str().empty() && logger != nullptr) { + configure(); + } + ss.str(std::string("")); + line = line.substr(2); + base::utils::Str::trim(line); + if (line.size() > 1) { + ELPP_INTERNAL_INFO(1, "Getting logger: '" << line << "'"); + logger = getLogger(line); + } + } else { + ss << line << "\n"; + } + } + if (!ss.str().empty() && logger != nullptr) { + configure(); + } +} + +bool Loggers::configureFromArg(const char* argKey) { +#if defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) + ELPP_UNUSED(argKey); +#else + if (!Helpers::commandLineArgs()->hasParamWithValue(argKey)) { + return false; + } + configureFromGlobal(Helpers::commandLineArgs()->getParamValue(argKey)); +#endif // defined(ELPP_DISABLE_CONFIGURATION_FROM_PROGRAM_ARGS) + return true; +} + +void Loggers::flushAll(void) { + ELPP->registeredLoggers()->flushAll(); +} + +void Loggers::setVerboseLevel(base::type::VerboseLevel level) { + ELPP->vRegistry()->setLevel(level); +} + +base::type::VerboseLevel Loggers::verboseLevel(void) { + return ELPP->vRegistry()->level(); +} + +void Loggers::setVModules(const char* modules) { + if (ELPP->vRegistry()->vModulesEnabled()) { + ELPP->vRegistry()->setModules(modules); + } +} + +void Loggers::clearVModules(void) { + ELPP->vRegistry()->clearModules(); +} + +// VersionInfo + +const std::string VersionInfo::version(void) { + return std::string("9.96.7"); +} +/// @brief Release date of current version +const std::string VersionInfo::releaseDate(void) { + return std::string("24-11-2018 0728hrs"); +} + +} // namespace el diff --git a/log/easylogging++.h b/log/easylogging++.h new file mode 100644 index 0000000..8b55082 --- /dev/null +++ b/log/easylogging++.h @@ -0,0 +1,4576 @@ +// +// Bismillah ar-Rahmaan ar-Raheem +// +// Easylogging++ v9.96.7 +// Single-header only, cross-platform logging library for C++ applications +// +// Copyright (c) 2012-2018 Amrayn Web Services +// Copyright (c) 2012-2018 @abumusamq +// +// This library is released under the MIT Licence. +// https://github.com/amrayn/easyloggingpp/blob/master/LICENSE +// +// https://amrayn.com +// http://muflihun.com +// + +#ifndef EASYLOGGINGPP_H +#define EASYLOGGINGPP_H +// Compilers and C++0x/C++11 Evaluation +#if __cplusplus >= 201103L +# define ELPP_CXX11 1 +#endif // __cplusplus >= 201103L +#if (defined(__GNUC__)) +# define ELPP_COMPILER_GCC 1 +#else +# define ELPP_COMPILER_GCC 0 +#endif +#if ELPP_COMPILER_GCC +# define ELPP_GCC_VERSION (__GNUC__ * 10000 \ ++ __GNUC_MINOR__ * 100 \ ++ __GNUC_PATCHLEVEL__) +# if defined(__GXX_EXPERIMENTAL_CXX0X__) +# define ELPP_CXX0X 1 +# endif +#endif +// Visual C++ +#if defined(_MSC_VER) +# define ELPP_COMPILER_MSVC 1 +#else +# define ELPP_COMPILER_MSVC 0 +#endif +#define ELPP_CRT_DBG_WARNINGS ELPP_COMPILER_MSVC +#if ELPP_COMPILER_MSVC +# if (_MSC_VER == 1600) +# define ELPP_CXX0X 1 +# elif(_MSC_VER >= 1700) +# define ELPP_CXX11 1 +# endif +#endif +// Clang++ +#if (defined(__clang__) && (__clang__ == 1)) +# define ELPP_COMPILER_CLANG 1 +#else +# define ELPP_COMPILER_CLANG 0 +#endif +#if ELPP_COMPILER_CLANG +# if __has_include() +# include // Make __GLIBCXX__ defined when using libstdc++ +# if !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 +# define ELPP_CLANG_SUPPORTS_THREAD +# endif // !defined(__GLIBCXX__) || __GLIBCXX__ >= 20150426 +# endif // __has_include() +#endif +#if (defined(__MINGW32__) || defined(__MINGW64__)) +# define ELPP_MINGW 1 +#else +# define ELPP_MINGW 0 +#endif +#if (defined(__CYGWIN__) && (__CYGWIN__ == 1)) +# define ELPP_CYGWIN 1 +#else +# define ELPP_CYGWIN 0 +#endif +#if (defined(__INTEL_COMPILER)) +# define ELPP_COMPILER_INTEL 1 +#else +# define ELPP_COMPILER_INTEL 0 +#endif +// Operating System Evaluation +// Windows +#if (defined(_WIN32) || defined(_WIN64)) +# define ELPP_OS_WINDOWS 1 +#else +# define ELPP_OS_WINDOWS 0 +#endif +// Linux +#if (defined(__linux) || defined(__linux__)) +# define ELPP_OS_LINUX 1 +#else +# define ELPP_OS_LINUX 0 +#endif +#if (defined(__APPLE__)) +# define ELPP_OS_MAC 1 +#else +# define ELPP_OS_MAC 0 +#endif +#if (defined(__FreeBSD__) || defined(__FreeBSD_kernel__)) +# define ELPP_OS_FREEBSD 1 +#else +# define ELPP_OS_FREEBSD 0 +#endif +#if (defined(__sun)) +# define ELPP_OS_SOLARIS 1 +#else +# define ELPP_OS_SOLARIS 0 +#endif +#if (defined(_AIX)) +# define ELPP_OS_AIX 1 +#else +# define ELPP_OS_AIX 0 +#endif +#if (defined(__NetBSD__)) +# define ELPP_OS_NETBSD 1 +#else +# define ELPP_OS_NETBSD 0 +#endif +#if defined(__EMSCRIPTEN__) +# define ELPP_OS_EMSCRIPTEN 1 +#else +# define ELPP_OS_EMSCRIPTEN 0 +#endif +#if (defined(__QNX__) || defined(__QNXNTO__)) +# define ELPP_OS_QNX 1 +#else +# define ELPP_OS_QNX 0 +#endif +// Unix +#if ((ELPP_OS_LINUX || ELPP_OS_MAC || ELPP_OS_FREEBSD || ELPP_OS_NETBSD || ELPP_OS_SOLARIS || ELPP_OS_AIX || ELPP_OS_EMSCRIPTEN || ELPP_OS_QNX) && (!ELPP_OS_WINDOWS)) +# define ELPP_OS_UNIX 1 +#else +# define ELPP_OS_UNIX 0 +#endif +#if (defined(__ANDROID__)) +# define ELPP_OS_ANDROID 1 +#else +# define ELPP_OS_ANDROID 0 +#endif +// Evaluating Cygwin as *nix OS +#if !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN +# undef ELPP_OS_UNIX +# undef ELPP_OS_LINUX +# define ELPP_OS_UNIX 1 +# define ELPP_OS_LINUX 1 +#endif // !ELPP_OS_UNIX && !ELPP_OS_WINDOWS && ELPP_CYGWIN +#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_INFO) +# define ELPP_INTERNAL_DEBUGGING_OUT_INFO std::cout +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_OUT_ERROR) +# define ELPP_INTERNAL_DEBUGGING_OUT_ERROR std::cerr +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_ENDL) +# define ELPP_INTERNAL_DEBUGGING_ENDL std::endl +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +#if !defined(ELPP_INTERNAL_DEBUGGING_MSG) +# define ELPP_INTERNAL_DEBUGGING_MSG(msg) msg +#endif // !defined(ELPP_INTERNAL_DEBUGGING_OUT) +// Internal Assertions and errors +#if !defined(ELPP_DISABLE_ASSERT) +# if (defined(ELPP_DEBUG_ASSERT_FAILURE)) +# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ +std::stringstream internalInfoStream; internalInfoStream << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ +<< "EASYLOGGING++ ASSERTION FAILED (LINE: " << __LINE__ << ") [" #expr << "] WITH MESSAGE \"" \ +<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" << ELPP_INTERNAL_DEBUGGING_ENDL; base::utils::abort(1, \ +"ELPP Assertion failure, please define ELPP_DEBUG_ASSERT_FAILURE"); } +# else +# define ELPP_ASSERT(expr, msg) if (!(expr)) { \ +std::stringstream internalInfoStream; internalInfoStream << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR\ +<< "ASSERTION FAILURE FROM EASYLOGGING++ (LINE: " \ +<< __LINE__ << ") [" #expr << "] WITH MESSAGE \"" << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << "\"" \ +<< ELPP_INTERNAL_DEBUGGING_ENDL; } +# endif // (defined(ELPP_DEBUG_ASSERT_FAILURE)) +#else +# define ELPP_ASSERT(x, y) +#endif //(!defined(ELPP_DISABLE_ASSERT) +#if ELPP_COMPILER_MSVC +# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ +{ char buff[256]; strerror_s(buff, 256, errno); \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << buff << " [" << errno << "]";} (void)0 +#else +# define ELPP_INTERNAL_DEBUGGING_WRITE_PERROR \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR << ": " << strerror(errno) << " [" << errno << "]"; (void)0 +#endif // ELPP_COMPILER_MSVC +#if defined(ELPP_DEBUG_ERRORS) +# if !defined(ELPP_INTERNAL_ERROR) +# define ELPP_INTERNAL_ERROR(msg, pe) { \ +std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_ERROR \ +<< "ERROR FROM EASYLOGGING++ (LINE: " << __LINE__ << ") " \ +<< ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) << ELPP_INTERNAL_DEBUGGING_ENDL; \ +if (pe) { ELPP_INTERNAL_DEBUGGING_OUT_ERROR << " "; ELPP_INTERNAL_DEBUGGING_WRITE_PERROR; }} (void)0 +# endif +#else +# undef ELPP_INTERNAL_INFO +# define ELPP_INTERNAL_ERROR(msg, pe) +#endif // defined(ELPP_DEBUG_ERRORS) +#if (defined(ELPP_DEBUG_INFO)) +# if !(defined(ELPP_INTERNAL_INFO_LEVEL)) +# define ELPP_INTERNAL_INFO_LEVEL 9 +# endif // !(defined(ELPP_INTERNAL_INFO_LEVEL)) +# if !defined(ELPP_INTERNAL_INFO) +# define ELPP_INTERNAL_INFO(lvl, msg) { if (lvl <= ELPP_INTERNAL_INFO_LEVEL) { \ +std::stringstream internalInfoStream; internalInfoStream << " " << msg; \ +ELPP_INTERNAL_DEBUGGING_OUT_INFO << ELPP_INTERNAL_DEBUGGING_MSG(internalInfoStream.str()) \ +<< ELPP_INTERNAL_DEBUGGING_ENDL; }} +# endif +#else +# undef ELPP_INTERNAL_INFO +# define ELPP_INTERNAL_INFO(lvl, msg) +#endif // (defined(ELPP_DEBUG_INFO)) +#if (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) +# if (ELPP_COMPILER_GCC && !ELPP_MINGW && !ELPP_CYGWIN && !ELPP_OS_ANDROID && !ELPP_OS_EMSCRIPTEN && !ELPP_OS_QNX) +# define ELPP_STACKTRACE 1 +# else +# if ELPP_COMPILER_MSVC +# pragma message("Stack trace not available for this compiler") +# else +# warning "Stack trace not available for this compiler"; +# endif // ELPP_COMPILER_MSVC +# define ELPP_STACKTRACE 0 +# endif // ELPP_COMPILER_GCC +#else +# define ELPP_STACKTRACE 0 +#endif // (defined(ELPP_FEATURE_ALL)) || (defined(ELPP_FEATURE_CRASH_LOG)) +// Miscellaneous macros +#define ELPP_UNUSED(x) (void)x +#if ELPP_OS_UNIX +// Log file permissions for unix-based systems +# define ELPP_LOG_PERMS S_IRUSR | S_IWUSR | S_IXUSR | S_IWGRP | S_IRGRP | S_IXGRP | S_IWOTH | S_IXOTH +#endif // ELPP_OS_UNIX +#if defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC +# if defined(ELPP_EXPORT_SYMBOLS) +# define ELPP_EXPORT __declspec(dllexport) +# else +# define ELPP_EXPORT __declspec(dllimport) +# endif // defined(ELPP_EXPORT_SYMBOLS) +#else +# define ELPP_EXPORT +#endif // defined(ELPP_AS_DLL) && ELPP_COMPILER_MSVC +// Some special functions that are VC++ specific +#undef STRTOK +#undef STRERROR +#undef STRCAT +#undef STRCPY +#if ELPP_CRT_DBG_WARNINGS +# define STRTOK(a, b, c) strtok_s(a, b, c) +# define STRERROR(a, b, c) strerror_s(a, b, c) +# define STRCAT(a, b, len) strcat_s(a, len, b) +# define STRCPY(a, b, len) strcpy_s(a, len, b) +#else +# define STRTOK(a, b, c) strtok(a, b) +# define STRERROR(a, b, c) strerror(c) +# define STRCAT(a, b, len) strcat(a, b) +# define STRCPY(a, b, len) strcpy(a, b) +#endif +// Compiler specific support evaluations +#if (ELPP_MINGW && !defined(ELPP_FORCE_USE_STD_THREAD)) +# define ELPP_USE_STD_THREADING 0 +#else +# if ((ELPP_COMPILER_CLANG && defined(ELPP_CLANG_SUPPORTS_THREAD)) || \ + (!ELPP_COMPILER_CLANG && defined(ELPP_CXX11)) || \ + defined(ELPP_FORCE_USE_STD_THREAD)) +# define ELPP_USE_STD_THREADING 1 +# else +# define ELPP_USE_STD_THREADING 0 +# endif +#endif +#undef ELPP_FINAL +#if ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) +# define ELPP_FINAL +#else +# define ELPP_FINAL final +#endif // ELPP_COMPILER_INTEL || (ELPP_GCC_VERSION < 40702) +#if defined(ELPP_EXPERIMENTAL_ASYNC) +# define ELPP_ASYNC_LOGGING 1 +#else +# define ELPP_ASYNC_LOGGING 0 +#endif // defined(ELPP_EXPERIMENTAL_ASYNC) +#if defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING +# define ELPP_THREADING_ENABLED 1 +#else +# define ELPP_THREADING_ENABLED 0 +#endif // defined(ELPP_THREAD_SAFE) || ELPP_ASYNC_LOGGING +// Function macro ELPP_FUNC +#undef ELPP_FUNC +#if ELPP_COMPILER_MSVC // Visual C++ +# define ELPP_FUNC __FUNCSIG__ +#elif ELPP_COMPILER_GCC // GCC +# define ELPP_FUNC __PRETTY_FUNCTION__ +#elif ELPP_COMPILER_INTEL // Intel C++ +# define ELPP_FUNC __PRETTY_FUNCTION__ +#elif ELPP_COMPILER_CLANG // Clang++ +# define ELPP_FUNC __PRETTY_FUNCTION__ +#else +# if defined(__func__) +# define ELPP_FUNC __func__ +# else +# define ELPP_FUNC "" +# endif // defined(__func__) +#endif // defined(_MSC_VER) +#undef ELPP_VARIADIC_TEMPLATES_SUPPORTED +// Keep following line commented until features are fixed +#define ELPP_VARIADIC_TEMPLATES_SUPPORTED \ +(ELPP_COMPILER_GCC || ELPP_COMPILER_CLANG || ELPP_COMPILER_INTEL || (ELPP_COMPILER_MSVC && _MSC_VER >= 1800)) +// Logging Enable/Disable macros +#if defined(ELPP_DISABLE_LOGS) +#define ELPP_LOGGING_ENABLED 0 +#else +#define ELPP_LOGGING_ENABLED 1 +#endif +#if (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_DEBUG_LOG 1 +#else +# define ELPP_DEBUG_LOG 0 +#endif // (!defined(ELPP_DISABLE_DEBUG_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_INFO_LOG 1 +#else +# define ELPP_INFO_LOG 0 +#endif // (!defined(ELPP_DISABLE_INFO_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_WARNING_LOG 1 +#else +# define ELPP_WARNING_LOG 0 +#endif // (!defined(ELPP_DISABLE_WARNING_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_ERROR_LOG 1 +#else +# define ELPP_ERROR_LOG 0 +#endif // (!defined(ELPP_DISABLE_ERROR_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_FATAL_LOG 1 +#else +# define ELPP_FATAL_LOG 0 +#endif // (!defined(ELPP_DISABLE_FATAL_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_TRACE_LOG 1 +#else +# define ELPP_TRACE_LOG 0 +#endif // (!defined(ELPP_DISABLE_TRACE_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) +# define ELPP_VERBOSE_LOG 1 +#else +# define ELPP_VERBOSE_LOG 0 +#endif // (!defined(ELPP_DISABLE_VERBOSE_LOGS) && (ELPP_LOGGING_ENABLED)) +#if (!(ELPP_CXX0X || ELPP_CXX11)) +# error "C++0x (or higher) support not detected! (Is `-std=c++11' missing?)" +#endif // (!(ELPP_CXX0X || ELPP_CXX11)) +// Headers +#if defined(ELPP_SYSLOG) +# include +#endif // defined(ELPP_SYSLOG) +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(ELPP_UNICODE) +# include +# if ELPP_OS_WINDOWS +# include +# endif // ELPP_OS_WINDOWS +#endif // defined(ELPP_UNICODE) +#ifdef HAVE_EXECINFO +# include +# include +#endif // ENABLE_EXECINFO +#if ELPP_OS_ANDROID +# include +#endif // ELPP_OS_ANDROID +#if ELPP_OS_UNIX +# include +# include +#elif ELPP_OS_WINDOWS +# include +# include +# if defined(WIN32_LEAN_AND_MEAN) +# if defined(ELPP_WINSOCK2) +# include +# else +# include +# endif // defined(ELPP_WINSOCK2) +# endif // defined(WIN32_LEAN_AND_MEAN) +#endif // ELPP_OS_UNIX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if ELPP_THREADING_ENABLED +# if ELPP_USE_STD_THREADING +# include +# include +# else +# if ELPP_OS_UNIX +# include +# endif // ELPP_OS_UNIX +# endif // ELPP_USE_STD_THREADING +#endif // ELPP_THREADING_ENABLED +#if ELPP_ASYNC_LOGGING +# if defined(ELPP_NO_SLEEP_FOR) +# include +# endif // defined(ELPP_NO_SLEEP_FOR) +# include +# include +# include +#endif // ELPP_ASYNC_LOGGING +#if defined(ELPP_STL_LOGGING) +// For logging STL based templates +# include +# include +# include +# include +# include +# include +# if defined(ELPP_LOG_STD_ARRAY) +# include +# endif // defined(ELPP_LOG_STD_ARRAY) +# if defined(ELPP_LOG_UNORDERED_SET) +# include +# endif // defined(ELPP_UNORDERED_SET) +#endif // defined(ELPP_STL_LOGGING) +#if defined(ELPP_QT_LOGGING) +// For logging Qt based classes & templates +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif // defined(ELPP_QT_LOGGING) +#if defined(ELPP_BOOST_LOGGING) +// For logging boost based classes & templates +# include +# include +# include +# include +# include +# include +# include +# include +#endif // defined(ELPP_BOOST_LOGGING) +#if defined(ELPP_WXWIDGETS_LOGGING) +// For logging wxWidgets based classes & templates +# include +#endif // defined(ELPP_WXWIDGETS_LOGGING) +#if defined(ELPP_UTC_DATETIME) +# define elpptime_r gmtime_r +# define elpptime_s gmtime_s +# define elpptime gmtime +#else +# define elpptime_r localtime_r +# define elpptime_s localtime_s +# define elpptime localtime +#endif // defined(ELPP_UTC_DATETIME) +// Forward declarations +namespace el { +class Logger; +class LogMessage; +class PerformanceTrackingData; +class Loggers; +class Helpers; +template class Callback; +class LogDispatchCallback; +class PerformanceTrackingCallback; +class LoggerRegistrationCallback; +class LogDispatchData; +namespace base { +class Storage; +class RegisteredLoggers; +class PerformanceTracker; +class MessageBuilder; +class Writer; +class PErrorWriter; +class LogDispatcher; +class DefaultLogBuilder; +class DefaultLogDispatchCallback; +#if ELPP_ASYNC_LOGGING +class AsyncLogDispatchCallback; +class AsyncDispatchWorker; +#endif // ELPP_ASYNC_LOGGING +class DefaultPerformanceTrackingCallback; +} // namespace base +} // namespace el +/// @brief Easylogging++ entry namespace +namespace el { +/// @brief Namespace containing base/internal functionality used by Easylogging++ +namespace base { +/// @brief Data types used by Easylogging++ +namespace type { +#undef ELPP_LITERAL +#undef ELPP_STRLEN +#undef ELPP_COUT +#if defined(ELPP_UNICODE) +# define ELPP_LITERAL(txt) L##txt +# define ELPP_STRLEN wcslen +# if defined ELPP_CUSTOM_COUT +# define ELPP_COUT ELPP_CUSTOM_COUT +# else +# define ELPP_COUT std::wcout +# endif // defined ELPP_CUSTOM_COUT +typedef wchar_t char_t; +typedef std::wstring string_t; +typedef std::wstringstream stringstream_t; +typedef std::wfstream fstream_t; +typedef std::wostream ostream_t; +#else +# define ELPP_LITERAL(txt) txt +# define ELPP_STRLEN strlen +# if defined ELPP_CUSTOM_COUT +# define ELPP_COUT ELPP_CUSTOM_COUT +# else +# define ELPP_COUT std::cout +# endif // defined ELPP_CUSTOM_COUT +typedef char char_t; +typedef std::string string_t; +typedef std::stringstream stringstream_t; +typedef std::fstream fstream_t; +typedef std::ostream ostream_t; +#endif // defined(ELPP_UNICODE) +#if defined(ELPP_CUSTOM_COUT_LINE) +# define ELPP_COUT_LINE(logLine) ELPP_CUSTOM_COUT_LINE(logLine) +#else +# define ELPP_COUT_LINE(logLine) logLine << std::flush +#endif // defined(ELPP_CUSTOM_COUT_LINE) +typedef unsigned int EnumType; +typedef unsigned short VerboseLevel; +typedef unsigned long int LineNumber; +typedef std::shared_ptr StoragePointer; +typedef std::shared_ptr LogDispatchCallbackPtr; +typedef std::shared_ptr PerformanceTrackingCallbackPtr; +typedef std::shared_ptr LoggerRegistrationCallbackPtr; +typedef std::unique_ptr PerformanceTrackerPtr; +} // namespace type +/// @brief Internal helper class that prevent copy constructor for class +/// +/// @detail When using this class simply inherit it privately +class NoCopy { + protected: + NoCopy(void) {} + private: + NoCopy(const NoCopy&); + NoCopy& operator=(const NoCopy&); +}; +/// @brief Internal helper class that makes all default constructors private. +/// +/// @detail This prevents initializing class making it static unless an explicit constructor is declared. +/// When using this class simply inherit it privately +class StaticClass { + private: + StaticClass(void); + StaticClass(const StaticClass&); + StaticClass& operator=(const StaticClass&); +}; +} // namespace base +/// @brief Represents enumeration for severity level used to determine level of logging +/// +/// @detail With Easylogging++, developers may disable or enable any level regardless of +/// what the severity is. Or they can choose to log using hierarchical logging flag +enum class Level : base::type::EnumType { + /// @brief Generic level that represents all the levels. Useful when setting global configuration for all levels + Global = 1, + /// @brief Information that can be useful to back-trace certain events - mostly useful than debug logs. + Trace = 2, + /// @brief Informational events most useful for developers to debug application + Debug = 4, + /// @brief Severe error information that will presumably abort application + Fatal = 8, + /// @brief Information representing errors in application but application will keep running + Error = 16, + /// @brief Useful when application has potentially harmful situations + Warning = 32, + /// @brief Information that can be highly useful and vary with verbose logging level. + Verbose = 64, + /// @brief Mainly useful to represent current progress of application + Info = 128, + /// @brief Represents unknown level + Unknown = 1010 +}; +} // namespace el +namespace std { +template<> struct hash { + public: + std::size_t operator()(const el::Level& l) const { + return hash {}(static_cast(l)); + } +}; +} +namespace el { +/// @brief Static class that contains helper functions for el::Level +class LevelHelper : base::StaticClass { + public: + /// @brief Represents minimum valid level. Useful when iterating through enum. + static const base::type::EnumType kMinValid = static_cast(Level::Trace); + /// @brief Represents maximum valid level. This is used internally and you should not need it. + static const base::type::EnumType kMaxValid = static_cast(Level::Info); + /// @brief Casts level to int, useful for iterating through enum. + static base::type::EnumType castToInt(Level level) { + return static_cast(level); + } + /// @brief Casts int(ushort) to level, useful for iterating through enum. + static Level castFromInt(base::type::EnumType l) { + return static_cast(l); + } + /// @brief Converts level to associated const char* + /// @return Upper case string based level. + static const char* convertToString(Level level); + /// @brief Converts from levelStr to Level + /// @param levelStr Upper case string based level. + /// Lower case is also valid but providing upper case is recommended. + static Level convertFromString(const char* levelStr); + /// @brief Applies specified function to each level starting from startIndex + /// @param startIndex initial value to start the iteration from. This is passed as pointer and + /// is left-shifted so this can be used inside function (fn) to represent current level. + /// @param fn function to apply with each level. This bool represent whether or not to stop iterating through levels. + static void forEachLevel(base::type::EnumType* startIndex, const std::function& fn); +}; +/// @brief Represents enumeration of ConfigurationType used to configure or access certain aspect +/// of logging +enum class ConfigurationType : base::type::EnumType { + /// @brief Determines whether or not corresponding level and logger of logging is enabled + /// You may disable all logs by using el::Level::Global + Enabled = 1, + /// @brief Whether or not to write corresponding log to log file + ToFile = 2, + /// @brief Whether or not to write corresponding level and logger log to standard output. + /// By standard output meaning termnal, command prompt etc + ToStandardOutput = 4, + /// @brief Determines format of logging corresponding level and logger. + Format = 8, + /// @brief Determines log file (full path) to write logs to for corresponding level and logger + Filename = 16, + /// @brief Specifies precision of the subsecond part. It should be within range (1-6). + SubsecondPrecision = 32, + /// @brief Alias of SubsecondPrecision (for backward compatibility) + MillisecondsWidth = SubsecondPrecision, + /// @brief Determines whether or not performance tracking is enabled. + /// + /// @detail This does not depend on logger or level. Performance tracking always uses 'performance' logger + PerformanceTracking = 64, + /// @brief Specifies log file max size. + /// + /// @detail If file size of corresponding log file (for corresponding level) is >= specified size, log file will + /// be truncated and re-initiated. + MaxLogFileSize = 128, + /// @brief Specifies number of log entries to hold until we flush pending log data + LogFlushThreshold = 256, + /// @brief Represents unknown configuration + Unknown = 1010 +}; +/// @brief Static class that contains helper functions for el::ConfigurationType +class ConfigurationTypeHelper : base::StaticClass { + public: + /// @brief Represents minimum valid configuration type. Useful when iterating through enum. + static const base::type::EnumType kMinValid = static_cast(ConfigurationType::Enabled); + /// @brief Represents maximum valid configuration type. This is used internally and you should not need it. + static const base::type::EnumType kMaxValid = static_cast(ConfigurationType::MaxLogFileSize); + /// @brief Casts configuration type to int, useful for iterating through enum. + static base::type::EnumType castToInt(ConfigurationType configurationType) { + return static_cast(configurationType); + } + /// @brief Casts int(ushort) to configuration type, useful for iterating through enum. + static ConfigurationType castFromInt(base::type::EnumType c) { + return static_cast(c); + } + /// @brief Converts configuration type to associated const char* + /// @returns Upper case string based configuration type. + static const char* convertToString(ConfigurationType configurationType); + /// @brief Converts from configStr to ConfigurationType + /// @param configStr Upper case string based configuration type. + /// Lower case is also valid but providing upper case is recommended. + static ConfigurationType convertFromString(const char* configStr); + /// @brief Applies specified function to each configuration type starting from startIndex + /// @param startIndex initial value to start the iteration from. This is passed by pointer and is left-shifted + /// so this can be used inside function (fn) to represent current configuration type. + /// @param fn function to apply with each configuration type. + /// This bool represent whether or not to stop iterating through configurations. + static inline void forEachConfigType(base::type::EnumType* startIndex, const std::function& fn); +}; +/// @brief Flags used while writing logs. This flags are set by user +enum class LoggingFlag : base::type::EnumType { + /// @brief Makes sure we have new line for each container log entry + NewLineForContainer = 1, + /// @brief Makes sure if -vmodule is used and does not specifies a module, then verbose + /// logging is allowed via that module. + AllowVerboseIfModuleNotSpecified = 2, + /// @brief When handling crashes by default, detailed crash reason will be logged as well + LogDetailedCrashReason = 4, + /// @brief Allows to disable application abortion when logged using FATAL level + DisableApplicationAbortOnFatalLog = 8, + /// @brief Flushes log with every log-entry (performance sensitive) - Disabled by default + ImmediateFlush = 16, + /// @brief Enables strict file rolling + StrictLogFileSizeCheck = 32, + /// @brief Make terminal output colorful for supported terminals + ColoredTerminalOutput = 64, + /// @brief Supports use of multiple logging in same macro, e.g, CLOG(INFO, "default", "network") + MultiLoggerSupport = 128, + /// @brief Disables comparing performance tracker's checkpoints + DisablePerformanceTrackingCheckpointComparison = 256, + /// @brief Disable VModules + DisableVModules = 512, + /// @brief Disable VModules extensions + DisableVModulesExtensions = 1024, + /// @brief Enables hierarchical logging + HierarchicalLogging = 2048, + /// @brief Creates logger automatically when not available + CreateLoggerAutomatically = 4096, + /// @brief Adds spaces b/w logs that separated by left-shift operator + AutoSpacing = 8192, + /// @brief Preserves time format and does not convert it to sec, hour etc (performance tracking only) + FixedTimeFormat = 16384, + // @brief Ignore SIGINT or crash + IgnoreSigInt = 32768, +}; +namespace base { +/// @brief Namespace containing constants used internally. +namespace consts { +static const char kFormatSpecifierCharValue = 'v'; +static const char kFormatSpecifierChar = '%'; +static const unsigned int kMaxLogPerCounter = 100000; +static const unsigned int kMaxLogPerContainer = 100; +static const unsigned int kDefaultSubsecondPrecision = 3; + +#ifdef ELPP_DEFAULT_LOGGER +static const char* kDefaultLoggerId = ELPP_DEFAULT_LOGGER; +#else +static const char* kDefaultLoggerId = "default"; +#endif + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +#ifdef ELPP_DEFAULT_PERFORMANCE_LOGGER +static const char* kPerformanceLoggerId = ELPP_DEFAULT_PERFORMANCE_LOGGER; +#else +static const char* kPerformanceLoggerId = "performance"; +#endif // ELPP_DEFAULT_PERFORMANCE_LOGGER +#endif + +#if defined(ELPP_SYSLOG) +static const char* kSysLogLoggerId = "syslog"; +#endif // defined(ELPP_SYSLOG) + +#if ELPP_OS_WINDOWS +static const char* kFilePathSeparator = "\\"; +#else +static const char* kFilePathSeparator = "/"; +#endif // ELPP_OS_WINDOWS + +static const std::size_t kSourceFilenameMaxLength = 100; +static const std::size_t kSourceLineMaxLength = 10; +static const Level kPerformanceTrackerDefaultLevel = Level::Info; +const struct { + double value; + const base::type::char_t* unit; +} kTimeFormats[] = { + { 1000.0f, ELPP_LITERAL("us") }, + { 1000.0f, ELPP_LITERAL("ms") }, + { 60.0f, ELPP_LITERAL("seconds") }, + { 60.0f, ELPP_LITERAL("minutes") }, + { 24.0f, ELPP_LITERAL("hours") }, + { 7.0f, ELPP_LITERAL("days") } +}; +static const int kTimeFormatsCount = sizeof(kTimeFormats) / sizeof(kTimeFormats[0]); +const struct { + int numb; + const char* name; + const char* brief; + const char* detail; +} kCrashSignals[] = { + // NOTE: Do not re-order, if you do please check CrashHandler(bool) constructor and CrashHandler::setHandler(..) + { + SIGABRT, "SIGABRT", "Abnormal termination", + "Program was abnormally terminated." + }, + { + SIGFPE, "SIGFPE", "Erroneous arithmetic operation", + "Arithmetic operation issue such as division by zero or operation resulting in overflow." + }, + { + SIGILL, "SIGILL", "Illegal instruction", + "Generally due to a corruption in the code or to an attempt to execute data." + }, + { + SIGSEGV, "SIGSEGV", "Invalid access to memory", + "Program is trying to read an invalid (unallocated, deleted or corrupted) or inaccessible memory." + }, + { + SIGINT, "SIGINT", "Interactive attention signal", + "Interruption generated (generally) by user or operating system." + }, +}; +static const int kCrashSignalsCount = sizeof(kCrashSignals) / sizeof(kCrashSignals[0]); +} // namespace consts +} // namespace base +typedef std::function PreRollOutCallback; +namespace base { +static inline void defaultPreRollOutCallback(const char*, std::size_t) {} +/// @brief Enum to represent timestamp unit +enum class TimestampUnit : base::type::EnumType { + Microsecond = 0, Millisecond = 1, Second = 2, Minute = 3, Hour = 4, Day = 5 +}; +/// @brief Format flags used to determine specifiers that are active for performance improvements. +enum class FormatFlags : base::type::EnumType { + DateTime = 1 << 1, + LoggerId = 1 << 2, + File = 1 << 3, + Line = 1 << 4, + Location = 1 << 5, + Function = 1 << 6, + User = 1 << 7, + Host = 1 << 8, + LogMessage = 1 << 9, + VerboseLevel = 1 << 10, + AppName = 1 << 11, + ThreadId = 1 << 12, + Level = 1 << 13, + FileBase = 1 << 14, + LevelShort = 1 << 15 +}; +/// @brief A subsecond precision class containing actual width and offset of the subsecond part +class SubsecondPrecision { + public: + SubsecondPrecision(void) { + init(base::consts::kDefaultSubsecondPrecision); + } + explicit SubsecondPrecision(int width) { + init(width); + } + bool operator==(const SubsecondPrecision& ssPrec) { + return m_width == ssPrec.m_width && m_offset == ssPrec.m_offset; + } + int m_width; + unsigned int m_offset; + private: + void init(int width); +}; +/// @brief Type alias of SubsecondPrecision +typedef SubsecondPrecision MillisecondsWidth; +/// @brief Namespace containing utility functions/static classes used internally +namespace utils { +/// @brief Deletes memory safely and points to null +template +static +typename std::enable_if::value, void>::type +safeDelete(T*& pointer) { + if (pointer == nullptr) + return; + delete pointer; + pointer = nullptr; +} +/// @brief Bitwise operations for C++11 strong enum class. This casts e into Flag_T and returns value after bitwise operation +/// Use these function as
flag = bitwise::Or(MyEnum::val1, flag);
+namespace bitwise { +template +static inline base::type::EnumType And(Enum e, base::type::EnumType flag) { + return static_cast(flag) & static_cast(e); +} +template +static inline base::type::EnumType Not(Enum e, base::type::EnumType flag) { + return static_cast(flag) & ~(static_cast(e)); +} +template +static inline base::type::EnumType Or(Enum e, base::type::EnumType flag) { + return static_cast(flag) | static_cast(e); +} +} // namespace bitwise +template +static inline void addFlag(Enum e, base::type::EnumType* flag) { + *flag = base::utils::bitwise::Or(e, *flag); +} +template +static inline void removeFlag(Enum e, base::type::EnumType* flag) { + *flag = base::utils::bitwise::Not(e, *flag); +} +template +static inline bool hasFlag(Enum e, base::type::EnumType flag) { + return base::utils::bitwise::And(e, flag) > 0x0; +} +} // namespace utils +namespace threading { +#if ELPP_THREADING_ENABLED +# if !ELPP_USE_STD_THREADING +namespace internal { +/// @brief A mutex wrapper for compiler that dont yet support std::recursive_mutex +class Mutex : base::NoCopy { + public: + Mutex(void) { +# if ELPP_OS_UNIX + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&m_underlyingMutex, &attr); + pthread_mutexattr_destroy(&attr); +# elif ELPP_OS_WINDOWS + InitializeCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + virtual ~Mutex(void) { +# if ELPP_OS_UNIX + pthread_mutex_destroy(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + DeleteCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline void lock(void) { +# if ELPP_OS_UNIX + pthread_mutex_lock(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + EnterCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline bool try_lock(void) { +# if ELPP_OS_UNIX + return (pthread_mutex_trylock(&m_underlyingMutex) == 0); +# elif ELPP_OS_WINDOWS + return TryEnterCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + inline void unlock(void) { +# if ELPP_OS_UNIX + pthread_mutex_unlock(&m_underlyingMutex); +# elif ELPP_OS_WINDOWS + LeaveCriticalSection(&m_underlyingMutex); +# endif // ELPP_OS_UNIX + } + + private: +# if ELPP_OS_UNIX + pthread_mutex_t m_underlyingMutex; +# elif ELPP_OS_WINDOWS + CRITICAL_SECTION m_underlyingMutex; +# endif // ELPP_OS_UNIX +}; +/// @brief Scoped lock for compiler that dont yet support std::lock_guard +template +class ScopedLock : base::NoCopy { + public: + explicit ScopedLock(M& mutex) { + m_mutex = &mutex; + m_mutex->lock(); + } + + virtual ~ScopedLock(void) { + m_mutex->unlock(); + } + private: + M* m_mutex; + ScopedLock(void); +}; +} // namespace internal +typedef base::threading::internal::Mutex Mutex; +typedef base::threading::internal::ScopedLock ScopedLock; +# else +typedef std::recursive_mutex Mutex; +typedef std::lock_guard ScopedLock; +# endif // !ELPP_USE_STD_THREADING +#else +namespace internal { +/// @brief Mutex wrapper used when multi-threading is disabled. +class NoMutex : base::NoCopy { + public: + NoMutex(void) {} + inline void lock(void) {} + inline bool try_lock(void) { + return true; + } + inline void unlock(void) {} +}; +/// @brief Lock guard wrapper used when multi-threading is disabled. +template +class NoScopedLock : base::NoCopy { + public: + explicit NoScopedLock(Mutex&) { + } + virtual ~NoScopedLock(void) { + } + private: + NoScopedLock(void); +}; +} // namespace internal +typedef base::threading::internal::NoMutex Mutex; +typedef base::threading::internal::NoScopedLock ScopedLock; +#endif // ELPP_THREADING_ENABLED +/// @brief Base of thread safe class, this class is inheritable-only +class ThreadSafe { + public: + virtual inline void acquireLock(void) ELPP_FINAL { m_mutex.lock(); } + virtual inline void releaseLock(void) ELPP_FINAL { m_mutex.unlock(); } + virtual inline base::threading::Mutex& lock(void) ELPP_FINAL { return m_mutex; } + protected: + ThreadSafe(void) {} + virtual ~ThreadSafe(void) {} + private: + base::threading::Mutex m_mutex; +}; + +#if ELPP_THREADING_ENABLED +# if !ELPP_USE_STD_THREADING +/// @brief Gets ID of currently running threading in windows systems. On unix, nothing is returned. +static std::string getCurrentThreadId(void) { + std::stringstream ss; +# if (ELPP_OS_WINDOWS) + ss << GetCurrentThreadId(); +# endif // (ELPP_OS_WINDOWS) + return ss.str(); +} +# else +/// @brief Gets ID of currently running threading using std::this_thread::get_id() +static std::string getCurrentThreadId(void) { + std::stringstream ss; + ss << std::this_thread::get_id(); + return ss.str(); +} +# endif // !ELPP_USE_STD_THREADING +#else +static inline std::string getCurrentThreadId(void) { + return std::string(); +} +#endif // ELPP_THREADING_ENABLED +} // namespace threading +namespace utils { +class File : base::StaticClass { + public: + /// @brief Creates new out file stream for specified filename. + /// @return Pointer to newly created fstream or nullptr + static base::type::fstream_t* newFileStream(const std::string& filename); + + /// @brief Gets size of file provided in stream + static std::size_t getSizeOfFile(base::type::fstream_t* fs); + + /// @brief Determines whether or not provided path exist in current file system + static bool pathExists(const char* path, bool considerFile = false); + + /// @brief Creates specified path on file system + /// @param path Path to create. + static bool createPath(const std::string& path); + /// @brief Extracts path of filename with leading slash + static std::string extractPathFromFilename(const std::string& fullPath, + const char* separator = base::consts::kFilePathSeparator); + /// @brief builds stripped filename and puts it in buff + static void buildStrippedFilename(const char* filename, char buff[], + std::size_t limit = base::consts::kSourceFilenameMaxLength); + /// @brief builds base filename and puts it in buff + static void buildBaseFilename(const std::string& fullPath, char buff[], + std::size_t limit = base::consts::kSourceFilenameMaxLength, + const char* separator = base::consts::kFilePathSeparator); +}; +/// @brief String utilities helper class used internally. You should not use it. +class Str : base::StaticClass { + public: + /// @brief Checks if character is digit. Dont use libc implementation of it to prevent locale issues. + static inline bool isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /// @brief Matches wildcards, '*' and '?' only supported. + static bool wildCardMatch(const char* str, const char* pattern); + + static std::string& ltrim(std::string& str); + static std::string& rtrim(std::string& str); + static std::string& trim(std::string& str); + + /// @brief Determines whether or not str starts with specified string + /// @param str String to check + /// @param start String to check against + /// @return Returns true if starts with specified string, false otherwise + static bool startsWith(const std::string& str, const std::string& start); + + /// @brief Determines whether or not str ends with specified string + /// @param str String to check + /// @param end String to check against + /// @return Returns true if ends with specified string, false otherwise + static bool endsWith(const std::string& str, const std::string& end); + + /// @brief Replaces all instances of replaceWhat with 'replaceWith'. Original variable is changed for performance. + /// @param [in,out] str String to replace from + /// @param replaceWhat Character to replace + /// @param replaceWith Character to replace with + /// @return Modified version of str + static std::string& replaceAll(std::string& str, char replaceWhat, char replaceWith); + + /// @brief Replaces all instances of 'replaceWhat' with 'replaceWith'. (String version) Replaces in place + /// @param str String to replace from + /// @param replaceWhat Character to replace + /// @param replaceWith Character to replace with + /// @return Modified (original) str + static std::string& replaceAll(std::string& str, const std::string& replaceWhat, + const std::string& replaceWith); + + static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const base::type::string_t& replaceWith); +#if defined(ELPP_UNICODE) + static void replaceFirstWithEscape(base::type::string_t& str, const base::type::string_t& replaceWhat, + const std::string& replaceWith); +#endif // defined(ELPP_UNICODE) + /// @brief Converts string to uppercase + /// @param str String to convert + /// @return Uppercase string + static std::string& toUpper(std::string& str); + + /// @brief Compares cstring equality - uses strcmp + static bool cStringEq(const char* s1, const char* s2); + + /// @brief Compares cstring equality (case-insensitive) - uses toupper(char) + /// Dont use strcasecmp because of CRT (VC++) + static bool cStringCaseEq(const char* s1, const char* s2); + + /// @brief Returns true if c exist in str + static bool contains(const char* str, char c); + + static char* convertAndAddToBuff(std::size_t n, int len, char* buf, const char* bufLim, bool zeroPadded = true); + static char* addToBuff(const char* str, char* buf, const char* bufLim); + static char* clearBuff(char buff[], std::size_t lim); + + /// @brief Converts wchar* to char* + /// NOTE: Need to free return value after use! + static char* wcharPtrToCharPtr(const wchar_t* line); +}; +/// @brief Operating System helper static class used internally. You should not use it. +class OS : base::StaticClass { + public: +#if ELPP_OS_WINDOWS + /// @brief Gets environment variables for Windows based OS. + /// We are not using getenv(const char*) because of CRT deprecation + /// @param varname Variable name to get environment variable value for + /// @return If variable exist the value of it otherwise nullptr + static const char* getWindowsEnvironmentVariable(const char* varname); +#endif // ELPP_OS_WINDOWS +#if ELPP_OS_ANDROID + /// @brief Reads android property value + static std::string getProperty(const char* prop); + + /// @brief Reads android device name + static std::string getDeviceName(void); +#endif // ELPP_OS_ANDROID + + /// @brief Runs command on terminal and returns the output. + /// + /// @detail This is applicable only on unix based systems, for all other OS, an empty string is returned. + /// @param command Bash command + /// @return Result of bash output or empty string if no result found. + static const std::string getBashOutput(const char* command); + + /// @brief Gets environment variable. This is cross-platform and CRT safe (for VC++) + /// @param variableName Environment variable name + /// @param defaultVal If no environment variable or value found the value to return by default + /// @param alternativeBashCommand If environment variable not found what would be alternative bash command + /// in order to look for value user is looking for. E.g, for 'user' alternative command will 'whoami' + static std::string getEnvironmentVariable(const char* variableName, const char* defaultVal, + const char* alternativeBashCommand = nullptr); + /// @brief Gets current username. + static std::string currentUser(void); + + /// @brief Gets current host name or computer name. + /// + /// @detail For android systems this is device name with its manufacturer and model separated by hyphen + static std::string currentHost(void); + /// @brief Whether or not terminal supports colors + static bool termSupportsColor(void); +}; +/// @brief Contains utilities for cross-platform date/time. This class make use of el::base::utils::Str +class DateTime : base::StaticClass { + public: + /// @brief Cross platform gettimeofday for Windows and unix platform. This can be used to determine current microsecond. + /// + /// @detail For unix system it uses gettimeofday(timeval*, timezone*) and for Windows, a separate implementation is provided + /// @param [in,out] tv Pointer that gets updated + static void gettimeofday(struct timeval* tv); + + /// @brief Gets current date and time with a subsecond part. + /// @param format User provided date/time format + /// @param ssPrec A pointer to base::SubsecondPrecision from configuration (non-null) + /// @returns string based date time in specified format. + static std::string getDateTime(const char* format, const base::SubsecondPrecision* ssPrec); + + /// @brief Converts timeval (struct from ctime) to string using specified format and subsecond precision + static std::string timevalToString(struct timeval tval, const char* format, + const el::base::SubsecondPrecision* ssPrec); + + /// @brief Formats time to get unit accordingly, units like second if > 1000 or minutes if > 60000 etc + static base::type::string_t formatTime(unsigned long long time, base::TimestampUnit timestampUnit); + + /// @brief Gets time difference in milli/micro second depending on timestampUnit + static unsigned long long getTimeDifference(const struct timeval& endTime, const struct timeval& startTime, + base::TimestampUnit timestampUnit); + + + static struct ::tm* buildTimeInfo(struct timeval* currTime, struct ::tm* timeInfo); + private: + static char* parseFormat(char* buf, std::size_t bufSz, const char* format, const struct tm* tInfo, + std::size_t msec, const base::SubsecondPrecision* ssPrec); +}; +/// @brief Command line arguments for application if specified using el::Helpers::setArgs(..) or START_EASYLOGGINGPP(..) +class CommandLineArgs { + public: + CommandLineArgs(void) { + setArgs(0, static_cast(nullptr)); + } + CommandLineArgs(int argc, const char** argv) { + setArgs(argc, argv); + } + CommandLineArgs(int argc, char** argv) { + setArgs(argc, argv); + } + virtual ~CommandLineArgs(void) {} + /// @brief Sets arguments and parses them + inline void setArgs(int argc, const char** argv) { + setArgs(argc, const_cast(argv)); + } + /// @brief Sets arguments and parses them + void setArgs(int argc, char** argv); + /// @brief Returns true if arguments contain paramKey with a value (separated by '=') + bool hasParamWithValue(const char* paramKey) const; + /// @brief Returns value of arguments + /// @see hasParamWithValue(const char*) + const char* getParamValue(const char* paramKey) const; + /// @brief Return true if arguments has a param (not having a value) i,e without '=' + bool hasParam(const char* paramKey) const; + /// @brief Returns true if no params available. This exclude argv[0] + bool empty(void) const; + /// @brief Returns total number of arguments. This exclude argv[0] + std::size_t size(void) const; + friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const CommandLineArgs& c); + + private: + int m_argc; + char** m_argv; + std::unordered_map m_paramsWithValue; + std::vector m_params; +}; +/// @brief Abstract registry (aka repository) that provides basic interface for pointer repository specified by T_Ptr type. +/// +/// @detail Most of the functions are virtual final methods but anything implementing this abstract class should implement +/// unregisterAll() and deepCopy(const AbstractRegistry&) and write registerNew() method according to container +/// and few more methods; get() to find element, unregister() to unregister single entry. +/// Please note that this is thread-unsafe and should also implement thread-safety mechanisms in implementation. +template +class AbstractRegistry : public base::threading::ThreadSafe { + public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + + /// @brief Default constructor + AbstractRegistry(void) {} + + /// @brief Move constructor that is useful for base classes + AbstractRegistry(AbstractRegistry&& sr) { + if (this == &sr) { + return; + } + unregisterAll(); + m_list = std::move(sr.m_list); + } + + bool operator==(const AbstractRegistry& other) { + if (size() != other.size()) { + return false; + } + for (std::size_t i = 0; i < m_list.size(); ++i) { + if (m_list.at(i) != other.m_list.at(i)) { + return false; + } + } + return true; + } + + bool operator!=(const AbstractRegistry& other) { + if (size() != other.size()) { + return true; + } + for (std::size_t i = 0; i < m_list.size(); ++i) { + if (m_list.at(i) != other.m_list.at(i)) { + return true; + } + } + return false; + } + + /// @brief Assignment move operator + AbstractRegistry& operator=(AbstractRegistry&& sr) { + if (this == &sr) { + return *this; + } + unregisterAll(); + m_list = std::move(sr.m_list); + return *this; + } + + virtual ~AbstractRegistry(void) { + } + + /// @return Iterator pointer from start of repository + virtual inline iterator begin(void) ELPP_FINAL { + return m_list.begin(); + } + + /// @return Iterator pointer from end of repository + virtual inline iterator end(void) ELPP_FINAL { + return m_list.end(); + } + + + /// @return Constant iterator pointer from start of repository + virtual inline const_iterator cbegin(void) const ELPP_FINAL { + return m_list.cbegin(); + } + + /// @return End of repository + virtual inline const_iterator cend(void) const ELPP_FINAL { + return m_list.cend(); + } + + /// @return Whether or not repository is empty + virtual inline bool empty(void) const ELPP_FINAL { + return m_list.empty(); + } + + /// @return Size of repository + virtual inline std::size_t size(void) const ELPP_FINAL { + return m_list.size(); + } + + /// @brief Returns underlying container by reference + virtual inline Container& list(void) ELPP_FINAL { + return m_list; + } + + /// @brief Returns underlying container by constant reference. + virtual inline const Container& list(void) const ELPP_FINAL { + return m_list; + } + + /// @brief Unregisters all the pointers from current repository. + virtual void unregisterAll(void) = 0; + + protected: + virtual void deepCopy(const AbstractRegistry&) = 0; + void reinitDeepCopy(const AbstractRegistry& sr) { + unregisterAll(); + deepCopy(sr); + } + + private: + Container m_list; +}; + +/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (non-predicate version) +/// +/// @detail NOTE: This is thread-unsafe implementation (although it contains lock function, it does not use these functions) +/// of AbstractRegistry. Any implementation of this class should be +/// explicitly (by using lock functions) +template +class Registry : public AbstractRegistry> { + public: + typedef typename Registry::iterator iterator; + typedef typename Registry::const_iterator const_iterator; + + Registry(void) {} + + /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. + Registry(const Registry& sr) : AbstractRegistry>() { + if (this == &sr) { + return; + } + this->reinitDeepCopy(sr); + } + + /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element + /// @see unregisterAll() + /// @see deepCopy(const AbstractRegistry&) + Registry& operator=(const Registry& sr) { + if (this == &sr) { + return *this; + } + this->reinitDeepCopy(sr); + return *this; + } + + virtual ~Registry(void) { + unregisterAll(); + } + + protected: + virtual void unregisterAll(void) ELPP_FINAL { + if (!this->empty()) { + for (auto&& curr : this->list()) { + base::utils::safeDelete(curr.second); + } + this->list().clear(); + } + } + +/// @brief Registers new registry to repository. + virtual void registerNew(const T_Key& uniqKey, T_Ptr* ptr) ELPP_FINAL { + unregister(uniqKey); + this->list().insert(std::make_pair(uniqKey, ptr)); + } + +/// @brief Unregisters single entry mapped to specified unique key + void unregister(const T_Key& uniqKey) { + T_Ptr* existing = get(uniqKey); + if (existing != nullptr) { + this->list().erase(uniqKey); + base::utils::safeDelete(existing); + } + } + +/// @brief Gets pointer from repository. If none found, nullptr is returned. + T_Ptr* get(const T_Key& uniqKey) { + iterator it = this->list().find(uniqKey); + return it == this->list().end() + ? nullptr + : it->second; + } + + private: + virtual void deepCopy(const AbstractRegistry>& sr) ELPP_FINAL { + for (const_iterator it = sr.cbegin(); it != sr.cend(); ++it) { + registerNew(it->first, new T_Ptr(*it->second)); + } + } +}; + +/// @brief A pointer registry mechanism to manage memory and provide search functionalities. (predicate version) +/// +/// @detail NOTE: This is thread-unsafe implementation of AbstractRegistry. Any implementation of this class +/// should be made thread-safe explicitly +template +class RegistryWithPred : public AbstractRegistry> { + public: + typedef typename RegistryWithPred::iterator iterator; + typedef typename RegistryWithPred::const_iterator const_iterator; + + RegistryWithPred(void) { + } + + virtual ~RegistryWithPred(void) { + unregisterAll(); + } + + /// @brief Copy constructor that is useful for base classes. Try to avoid this constructor, use move constructor. + RegistryWithPred(const RegistryWithPred& sr) : AbstractRegistry>() { + if (this == &sr) { + return; + } + this->reinitDeepCopy(sr); + } + + /// @brief Assignment operator that unregisters all the existing registries and deeply copies each of repo element + /// @see unregisterAll() + /// @see deepCopy(const AbstractRegistry&) + RegistryWithPred& operator=(const RegistryWithPred& sr) { + if (this == &sr) { + return *this; + } + this->reinitDeepCopy(sr); + return *this; + } + + friend base::type::ostream_t& operator<<(base::type::ostream_t& os, const RegistryWithPred& sr) { + for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { + os << ELPP_LITERAL(" ") << **it << ELPP_LITERAL("\n"); + } + return os; + } + + protected: + virtual void unregisterAll(void) ELPP_FINAL { + if (!this->empty()) { + for (auto&& curr : this->list()) { + base::utils::safeDelete(curr); + } + this->list().clear(); + } + } + + virtual void unregister(T_Ptr*& ptr) ELPP_FINAL { + if (ptr) { + iterator iter = this->begin(); + for (; iter != this->end(); ++iter) { + if (ptr == *iter) { + break; + } + } + if (iter != this->end() && *iter != nullptr) { + this->list().erase(iter); + base::utils::safeDelete(*iter); + } + } + } + + virtual inline void registerNew(T_Ptr* ptr) ELPP_FINAL { + this->list().push_back(ptr); + } + +/// @brief Gets pointer from repository with specified arguments. Arguments are passed to predicate +/// in order to validate pointer. + template + T_Ptr* get(const T& arg1, const T2 arg2) { + iterator iter = std::find_if(this->list().begin(), this->list().end(), Pred(arg1, arg2)); + if (iter != this->list().end() && *iter != nullptr) { + return *iter; + } + return nullptr; + } + + private: + virtual void deepCopy(const AbstractRegistry>& sr) { + for (const_iterator it = sr.list().begin(); it != sr.list().end(); ++it) { + registerNew(new T_Ptr(**it)); + } + } +}; +class Utils { + public: + template + static bool installCallback(const std::string& id, std::unordered_map* mapT) { + if (mapT->find(id) == mapT->end()) { + mapT->insert(std::make_pair(id, TPtr(new T()))); + return true; + } + return false; + } + + template + static void uninstallCallback(const std::string& id, std::unordered_map* mapT) { + if (mapT->find(id) != mapT->end()) { + mapT->erase(id); + } + } + + template + static T* callback(const std::string& id, std::unordered_map* mapT) { + typename std::unordered_map::iterator iter = mapT->find(id); + if (iter != mapT->end()) { + return static_cast(iter->second.get()); + } + return nullptr; + } +}; +} // namespace utils +} // namespace base +/// @brief Base of Easylogging++ friendly class +/// +/// @detail After inheriting this class publicly, implement pure-virtual function `void log(std::ostream&) const` +class Loggable { + public: + virtual ~Loggable(void) {} + virtual void log(el::base::type::ostream_t&) const = 0; + private: + friend inline el::base::type::ostream_t& operator<<(el::base::type::ostream_t& os, const Loggable& loggable) { + loggable.log(os); + return os; + } +}; +namespace base { +/// @brief Represents log format containing flags and date format. This is used internally to start initial log +class LogFormat : public Loggable { + public: + LogFormat(void); + LogFormat(Level level, const base::type::string_t& format); + LogFormat(const LogFormat& logFormat); + LogFormat(LogFormat&& logFormat); + LogFormat& operator=(const LogFormat& logFormat); + virtual ~LogFormat(void) {} + bool operator==(const LogFormat& other); + + /// @brief Updates format to be used while logging. + /// @param userFormat User provided format + void parseFromFormat(const base::type::string_t& userFormat); + + inline Level level(void) const { + return m_level; + } + + inline const base::type::string_t& userFormat(void) const { + return m_userFormat; + } + + inline const base::type::string_t& format(void) const { + return m_format; + } + + inline const std::string& dateTimeFormat(void) const { + return m_dateTimeFormat; + } + + inline base::type::EnumType flags(void) const { + return m_flags; + } + + inline bool hasFlag(base::FormatFlags flag) const { + return base::utils::hasFlag(flag, m_flags); + } + + virtual void log(el::base::type::ostream_t& os) const { + os << m_format; + } + + protected: + /// @brief Updates date time format if available in currFormat. + /// @param index Index where %datetime, %date or %time was found + /// @param [in,out] currFormat current format that is being used to format + virtual void updateDateFormat(std::size_t index, base::type::string_t& currFormat) ELPP_FINAL; + + /// @brief Updates %level from format. This is so that we dont have to do it at log-writing-time. It uses m_format and m_level + virtual void updateFormatSpec(void) ELPP_FINAL; + + inline void addFlag(base::FormatFlags flag) { + base::utils::addFlag(flag, &m_flags); + } + + private: + Level m_level; + base::type::string_t m_userFormat; + base::type::string_t m_format; + std::string m_dateTimeFormat; + base::type::EnumType m_flags; + std::string m_currentUser; + std::string m_currentHost; + friend class el::Logger; // To resolve loggerId format specifier easily +}; +} // namespace base +/// @brief Resolving function for format specifier +typedef std::function FormatSpecifierValueResolver; +/// @brief User-provided custom format specifier +/// @see el::Helpers::installCustomFormatSpecifier +/// @see FormatSpecifierValueResolver +class CustomFormatSpecifier { + public: + CustomFormatSpecifier(const char* formatSpecifier, const FormatSpecifierValueResolver& resolver) : + m_formatSpecifier(formatSpecifier), m_resolver(resolver) {} + inline const char* formatSpecifier(void) const { + return m_formatSpecifier; + } + inline const FormatSpecifierValueResolver& resolver(void) const { + return m_resolver; + } + inline bool operator==(const char* formatSpecifier) { + return strcmp(m_formatSpecifier, formatSpecifier) == 0; + } + + private: + const char* m_formatSpecifier; + FormatSpecifierValueResolver m_resolver; +}; +/// @brief Represents single configuration that has representing level, configuration type and a string based value. +/// +/// @detail String based value means any value either its boolean, integer or string itself, it will be embedded inside quotes +/// and will be parsed later. +/// +/// Consider some examples below: +/// * el::Configuration confEnabledInfo(el::Level::Info, el::ConfigurationType::Enabled, "true"); +/// * el::Configuration confMaxLogFileSizeInfo(el::Level::Info, el::ConfigurationType::MaxLogFileSize, "2048"); +/// * el::Configuration confFilenameInfo(el::Level::Info, el::ConfigurationType::Filename, "/var/log/my.log"); +class Configuration : public Loggable { + public: + Configuration(const Configuration& c); + Configuration& operator=(const Configuration& c); + + virtual ~Configuration(void) { + } + + /// @brief Full constructor used to sets value of configuration + Configuration(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Gets level of current configuration + inline Level level(void) const { + return m_level; + } + + /// @brief Gets configuration type of current configuration + inline ConfigurationType configurationType(void) const { + return m_configurationType; + } + + /// @brief Gets string based configuration value + inline const std::string& value(void) const { + return m_value; + } + + /// @brief Set string based configuration value + /// @param value Value to set. Values have to be std::string; For boolean values use "true", "false", for any integral values + /// use them in quotes. They will be parsed when configuring + inline void setValue(const std::string& value) { + m_value = value; + } + + virtual void log(el::base::type::ostream_t& os) const; + + /// @brief Used to find configuration from configuration (pointers) repository. Avoid using it. + class Predicate { + public: + Predicate(Level level, ConfigurationType configurationType); + + bool operator()(const Configuration* conf) const; + + private: + Level m_level; + ConfigurationType m_configurationType; + }; + + private: + Level m_level; + ConfigurationType m_configurationType; + std::string m_value; +}; + +/// @brief Thread-safe Configuration repository +/// +/// @detail This repository represents configurations for all the levels and configuration type mapped to a value. +class Configurations : public base::utils::RegistryWithPred { + public: + /// @brief Default constructor with empty repository + Configurations(void); + + /// @brief Constructor used to set configurations using configuration file. + /// @param configurationFile Full path to configuration file + /// @param useDefaultsForRemaining Lets you set the remaining configurations to default. + /// @param base If provided, this configuration will be based off existing repository that this argument is pointing to. + /// @see parseFromFile(const std::string&, Configurations* base) + /// @see setRemainingToDefault() + Configurations(const std::string& configurationFile, bool useDefaultsForRemaining = true, + Configurations* base = nullptr); + + virtual ~Configurations(void) { + } + + /// @brief Parses configuration from file. + /// @param configurationFile Full path to configuration file + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration file. + /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you + /// do not proceed without successful parse. + bool parseFromFile(const std::string& configurationFile, Configurations* base = nullptr); + + /// @brief Parse configurations from configuration string. + /// + /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary + /// new line characters are provided. + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration text. + /// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you + /// do not proceed without successful parse. + bool parseFromText(const std::string& configurationsString, Configurations* base = nullptr); + + /// @brief Sets configuration based-off an existing configurations. + /// @param base Pointer to existing configurations. + void setFromBase(Configurations* base); + + /// @brief Determines whether or not specified configuration type exists in the repository. + /// + /// @detail Returns as soon as first level is found. + /// @param configurationType Type of configuration to check existence for. + bool hasConfiguration(ConfigurationType configurationType); + + /// @brief Determines whether or not specified configuration type exists for specified level + /// @param level Level to check + /// @param configurationType Type of configuration to check existence for. + bool hasConfiguration(Level level, ConfigurationType configurationType); + + /// @brief Sets value of configuration for specified level. + /// + /// @detail Any existing configuration for specified level will be replaced. Also note that configuration types + /// ConfigurationType::SubsecondPrecision and ConfigurationType::PerformanceTracking will be ignored if not set for + /// Level::Global because these configurations are not dependant on level. + /// @param level Level to set configuration for (el::Level). + /// @param configurationType Type of configuration (el::ConfigurationType) + /// @param value A string based value. Regardless of what the data type of configuration is, it will always be string + /// from users' point of view. This is then parsed later to be used internally. + /// @see Configuration::setValue(const std::string& value) + /// @see el::Level + /// @see el::ConfigurationType + void set(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Sets single configuration based on other single configuration. + /// @see set(Level level, ConfigurationType configurationType, const std::string& value) + void set(Configuration* conf); + + inline Configuration* get(Level level, ConfigurationType configurationType) { + base::threading::ScopedLock scopedLock(lock()); + return RegistryWithPred::get(level, configurationType); + } + + /// @brief Sets configuration for all levels. + /// @param configurationType Type of configuration + /// @param value String based value + /// @see Configurations::set(Level level, ConfigurationType configurationType, const std::string& value) + inline void setGlobally(ConfigurationType configurationType, const std::string& value) { + setGlobally(configurationType, value, false); + } + + /// @brief Clears repository so that all the configurations are unset + inline void clear(void) { + base::threading::ScopedLock scopedLock(lock()); + unregisterAll(); + } + + /// @brief Gets configuration file used in parsing this configurations. + /// + /// @detail If this repository was set manually or by text this returns empty string. + inline const std::string& configurationFile(void) const { + return m_configurationFile; + } + + /// @brief Sets configurations to "factory based" configurations. + void setToDefault(void); + + /// @brief Lets you set the remaining configurations to default. + /// + /// @detail By remaining, it means that the level/type a configuration does not exist for. + /// This function is useful when you want to minimize chances of failures, e.g, if you have a configuration file that sets + /// configuration for all the configurations except for Enabled or not, we use this so that ENABLED is set to default i.e, + /// true. If you dont do this explicitly (either by calling this function or by using second param in Constructor + /// and try to access a value, an error is thrown + void setRemainingToDefault(void); + + /// @brief Parser used internally to parse configurations from file or text. + /// + /// @detail This class makes use of base::utils::Str. + /// You should not need this unless you are working on some tool for Easylogging++ + class Parser : base::StaticClass { + public: + /// @brief Parses configuration from file. + /// @param configurationFile Full path to configuration file + /// @param sender Sender configurations pointer. Usually 'this' is used from calling class + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration file. + /// @return True if successfully parsed, false otherwise. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you + /// do not proceed without successful parse. + static bool parseFromFile(const std::string& configurationFile, Configurations* sender, + Configurations* base = nullptr); + + /// @brief Parse configurations from configuration string. + /// + /// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary + /// new line characters are provided. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you + /// do not proceed without successful parse (This is recommended) + /// @param configurationsString the configuration in plain text format + /// @param sender Sender configurations pointer. Usually 'this' is used from calling class + /// @param base Configurations to base new configuration repository off. This value is used when you want to use + /// existing Configurations to base all the values and then set rest of configuration via configuration text. + /// @return True if successfully parsed, false otherwise. + static bool parseFromText(const std::string& configurationsString, Configurations* sender, + Configurations* base = nullptr); + + private: + friend class el::Loggers; + static void ignoreComments(std::string* line); + static bool isLevel(const std::string& line); + static bool isComment(const std::string& line); + static inline bool isConfig(const std::string& line); + static bool parseLine(std::string* line, std::string* currConfigStr, std::string* currLevelStr, Level* currLevel, + Configurations* conf); + }; + + private: + std::string m_configurationFile; + bool m_isFromFile; + friend class el::Loggers; + + /// @brief Unsafely sets configuration if does not already exist + void unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Thread unsafe set + void unsafeSet(Level level, ConfigurationType configurationType, const std::string& value); + + /// @brief Sets configurations for all levels including Level::Global if includeGlobalLevel is true + /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) + void setGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); + + /// @brief Sets configurations (Unsafely) for all levels including Level::Global if includeGlobalLevel is true + /// @see Configurations::setGlobally(ConfigurationType configurationType, const std::string& value) + void unsafeSetGlobally(ConfigurationType configurationType, const std::string& value, bool includeGlobalLevel); +}; + +namespace base { +typedef std::shared_ptr FileStreamPtr; +typedef std::unordered_map LogStreamsReferenceMap; +typedef std::shared_ptr LogStreamsReferenceMapPtr; +/// @brief Configurations with data types. +/// +/// @detail el::Configurations have string based values. This is whats used internally in order to read correct configurations. +/// This is to perform faster while writing logs using correct configurations. +/// +/// This is thread safe and final class containing non-virtual destructor (means nothing should inherit this class) +class TypedConfigurations : public base::threading::ThreadSafe { + public: + /// @brief Constructor to initialize (construct) the object off el::Configurations + /// @param configurations Configurations pointer/reference to base this typed configurations off. + /// @param logStreamsReference Use ELPP->registeredLoggers()->logStreamsReference() + TypedConfigurations(Configurations* configurations, LogStreamsReferenceMapPtr logStreamsReference); + + TypedConfigurations(const TypedConfigurations& other); + + virtual ~TypedConfigurations(void) { + } + + const Configurations* configurations(void) const { + return m_configurations; + } + + bool enabled(Level level); + bool toFile(Level level); + const std::string& filename(Level level); + bool toStandardOutput(Level level); + const base::LogFormat& logFormat(Level level); + const base::SubsecondPrecision& subsecondPrecision(Level level = Level::Global); + const base::MillisecondsWidth& millisecondsWidth(Level level = Level::Global); + bool performanceTracking(Level level = Level::Global); + base::type::fstream_t* fileStream(Level level); + std::size_t maxLogFileSize(Level level); + std::size_t logFlushThreshold(Level level); + + private: + Configurations* m_configurations; + std::unordered_map m_enabledMap; + std::unordered_map m_toFileMap; + std::unordered_map m_filenameMap; + std::unordered_map m_toStandardOutputMap; + std::unordered_map m_logFormatMap; + std::unordered_map m_subsecondPrecisionMap; + std::unordered_map m_performanceTrackingMap; + std::unordered_map m_fileStreamMap; + std::unordered_map m_maxLogFileSizeMap; + std::unordered_map m_logFlushThresholdMap; + LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + + friend class el::Helpers; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::DefaultLogDispatchCallback; + friend class el::base::LogDispatcher; + + template + inline Conf_T getConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeGetConfigByVal(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope + } + + template + inline Conf_T& getConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeGetConfigByRef(level, confMap, confName); // This is not unsafe anymore - mutex locked in scope + } + + template + Conf_T unsafeGetConfigByVal(Level level, const std::unordered_map* confMap, const char* confName) { + ELPP_UNUSED(confName); + typename std::unordered_map::const_iterator it = confMap->find(level); + if (it == confMap->end()) { + try { + return confMap->at(Level::Global); + } catch (...) { + ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" + << LevelHelper::convertToString(level) << "]" + << std::endl << "Please ensure you have properly configured logger.", false); + return Conf_T(); + } + } + return it->second; + } + + template + Conf_T& unsafeGetConfigByRef(Level level, std::unordered_map* confMap, const char* confName) { + ELPP_UNUSED(confName); + typename std::unordered_map::iterator it = confMap->find(level); + if (it == confMap->end()) { + try { + return confMap->at(Level::Global); + } catch (...) { + ELPP_INTERNAL_ERROR("Unable to get configuration [" << confName << "] for level [" + << LevelHelper::convertToString(level) << "]" + << std::endl << "Please ensure you have properly configured logger.", false); + } + } + return it->second; + } + + template + void setValue(Level level, const Conf_T& value, std::unordered_map* confMap, + bool includeGlobalLevel = true) { + // If map is empty and we are allowed to add into generic level (Level::Global), do it! + if (confMap->empty() && includeGlobalLevel) { + confMap->insert(std::make_pair(Level::Global, value)); + return; + } + // If same value exist in generic level already, dont add it to explicit level + typename std::unordered_map::iterator it = confMap->find(Level::Global); + if (it != confMap->end() && it->second == value) { + return; + } + // Now make sure we dont double up values if we really need to add it to explicit level + it = confMap->find(level); + if (it == confMap->end()) { + // Value not found for level, add new + confMap->insert(std::make_pair(level, value)); + } else { + // Value found, just update value + confMap->at(level) = value; + } + } + + void build(Configurations* configurations); + unsigned long getULong(std::string confVal); + std::string resolveFilename(const std::string& filename); + void insertFile(Level level, const std::string& fullFilename); + bool unsafeValidateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback); + + inline bool validateFileRolling(Level level, const PreRollOutCallback& preRollOutCallback) { + base::threading::ScopedLock scopedLock(lock()); + return unsafeValidateFileRolling(level, preRollOutCallback); + } +}; +/// @brief Class that keeps record of current line hit for occasional logging +class HitCounter { + public: + HitCounter(void) : + m_filename(""), + m_lineNumber(0), + m_hitCounts(0) { + } + + HitCounter(const char* filename, base::type::LineNumber lineNumber) : + m_filename(filename), + m_lineNumber(lineNumber), + m_hitCounts(0) { + } + + HitCounter(const HitCounter& hitCounter) : + m_filename(hitCounter.m_filename), + m_lineNumber(hitCounter.m_lineNumber), + m_hitCounts(hitCounter.m_hitCounts) { + } + + HitCounter& operator=(const HitCounter& hitCounter) { + if (&hitCounter != this) { + m_filename = hitCounter.m_filename; + m_lineNumber = hitCounter.m_lineNumber; + m_hitCounts = hitCounter.m_hitCounts; + } + return *this; + } + + virtual ~HitCounter(void) { + } + + /// @brief Resets location of current hit counter + inline void resetLocation(const char* filename, base::type::LineNumber lineNumber) { + m_filename = filename; + m_lineNumber = lineNumber; + } + + /// @brief Validates hit counts and resets it if necessary + inline void validateHitCounts(std::size_t n) { + if (m_hitCounts >= base::consts::kMaxLogPerCounter) { + m_hitCounts = (n >= 1 ? base::consts::kMaxLogPerCounter % n : 0); + } + ++m_hitCounts; + } + + inline const char* filename(void) const { + return m_filename; + } + + inline base::type::LineNumber lineNumber(void) const { + return m_lineNumber; + } + + inline std::size_t hitCounts(void) const { + return m_hitCounts; + } + + inline void increment(void) { + ++m_hitCounts; + } + + class Predicate { + public: + Predicate(const char* filename, base::type::LineNumber lineNumber) + : m_filename(filename), + m_lineNumber(lineNumber) { + } + inline bool operator()(const HitCounter* counter) { + return ((counter != nullptr) && + (strcmp(counter->m_filename, m_filename) == 0) && + (counter->m_lineNumber == m_lineNumber)); + } + + private: + const char* m_filename; + base::type::LineNumber m_lineNumber; + }; + + private: + const char* m_filename; + base::type::LineNumber m_lineNumber; + std::size_t m_hitCounts; +}; +/// @brief Repository for hit counters used across the application +class RegisteredHitCounters : public base::utils::RegistryWithPred { + public: + /// @brief Validates counter for every N, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateEveryN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Validates counter for hits >= N, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateAfterN(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Validates counter for hits are <= n, i.e, registers new if does not exist otherwise updates original one + /// @return True if validation resulted in triggering hit. Meaning logs should be written everytime true is returned + bool validateNTimes(const char* filename, base::type::LineNumber lineNumber, std::size_t n); + + /// @brief Gets hit counter registered at specified position + inline const base::HitCounter* getCounter(const char* filename, base::type::LineNumber lineNumber) { + base::threading::ScopedLock scopedLock(lock()); + return get(filename, lineNumber); + } +}; +/// @brief Action to be taken for dispatching +enum class DispatchAction : base::type::EnumType { + None = 1, NormalLog = 2, SysLog = 4 +}; +} // namespace base +template +class Callback : protected base::threading::ThreadSafe { + public: + Callback(void) : m_enabled(true) {} + inline bool enabled(void) const { + return m_enabled; + } + inline void setEnabled(bool enabled) { + base::threading::ScopedLock scopedLock(lock()); + m_enabled = enabled; + } + protected: + virtual void handle(const T* handlePtr) = 0; + private: + bool m_enabled; +}; +class LogDispatchData { + public: + LogDispatchData() : m_logMessage(nullptr), m_dispatchAction(base::DispatchAction::None) {} + inline const LogMessage* logMessage(void) const { + return m_logMessage; + } + inline base::DispatchAction dispatchAction(void) const { + return m_dispatchAction; + } + inline void setLogMessage(LogMessage* logMessage) { + m_logMessage = logMessage; + } + inline void setDispatchAction(base::DispatchAction dispatchAction) { + m_dispatchAction = dispatchAction; + } + private: + LogMessage* m_logMessage; + base::DispatchAction m_dispatchAction; + friend class base::LogDispatcher; + +}; +class LogDispatchCallback : public Callback { + protected: + virtual void handle(const LogDispatchData* data); + base::threading::Mutex& fileHandle(const LogDispatchData* data); + private: + friend class base::LogDispatcher; + std::unordered_map> m_fileLocks; + base::threading::Mutex m_fileLocksMapLock; +}; +class PerformanceTrackingCallback : public Callback { + private: + friend class base::PerformanceTracker; +}; +class LoggerRegistrationCallback : public Callback { + private: + friend class base::RegisteredLoggers; +}; +class LogBuilder : base::NoCopy { + public: + LogBuilder() : m_termSupportsColor(base::utils::OS::termSupportsColor()) {} + virtual ~LogBuilder(void) { + ELPP_INTERNAL_INFO(3, "Destroying log builder...") + } + virtual base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const = 0; + void convertToColoredOutput(base::type::string_t* logLine, Level level); + private: + bool m_termSupportsColor; + friend class el::base::DefaultLogDispatchCallback; +}; +typedef std::shared_ptr LogBuilderPtr; +/// @brief Represents a logger holding ID and configurations we need to write logs +/// +/// @detail This class does not write logs itself instead its used by writer to read configurations from. +class Logger : public base::threading::ThreadSafe, public Loggable { + public: + Logger(const std::string& id, base::LogStreamsReferenceMapPtr logStreamsReference); + Logger(const std::string& id, const Configurations& configurations, base::LogStreamsReferenceMapPtr logStreamsReference); + Logger(const Logger& logger); + Logger& operator=(const Logger& logger); + + virtual ~Logger(void) { + base::utils::safeDelete(m_typedConfigurations); + } + + virtual inline void log(el::base::type::ostream_t& os) const { + os << m_id.c_str(); + } + + /// @brief Configures the logger using specified configurations. + void configure(const Configurations& configurations); + + /// @brief Reconfigures logger using existing configurations + void reconfigure(void); + + inline const std::string& id(void) const { + return m_id; + } + + inline const std::string& parentApplicationName(void) const { + return m_parentApplicationName; + } + + inline void setParentApplicationName(const std::string& parentApplicationName) { + m_parentApplicationName = parentApplicationName; + } + + inline Configurations* configurations(void) { + return &m_configurations; + } + + inline base::TypedConfigurations* typedConfigurations(void) { + return m_typedConfigurations; + } + + static bool isValidId(const std::string& id); + + /// @brief Flushes logger to sync all log files for all levels + void flush(void); + + void flush(Level level, base::type::fstream_t* fs); + + inline bool isFlushNeeded(Level level) { + return ++m_unflushedCount.find(level)->second >= m_typedConfigurations->logFlushThreshold(level); + } + + inline LogBuilder* logBuilder(void) const { + return m_logBuilder.get(); + } + + inline void setLogBuilder(const LogBuilderPtr& logBuilder) { + m_logBuilder = logBuilder; + } + + inline bool enabled(Level level) const { + return m_typedConfigurations->enabled(level); + } + +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED +# define LOGGER_LEVEL_WRITERS_SIGNATURES(FUNCTION_NAME)\ +template \ +inline void FUNCTION_NAME(const char*, const T&, const Args&...);\ +template \ +inline void FUNCTION_NAME(const T&); + + template + inline void verbose(int, const char*, const T&, const Args&...); + + template + inline void verbose(int, const T&); + + LOGGER_LEVEL_WRITERS_SIGNATURES(info) + LOGGER_LEVEL_WRITERS_SIGNATURES(debug) + LOGGER_LEVEL_WRITERS_SIGNATURES(warn) + LOGGER_LEVEL_WRITERS_SIGNATURES(error) + LOGGER_LEVEL_WRITERS_SIGNATURES(fatal) + LOGGER_LEVEL_WRITERS_SIGNATURES(trace) +# undef LOGGER_LEVEL_WRITERS_SIGNATURES +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED + private: + std::string m_id; + base::TypedConfigurations* m_typedConfigurations; + base::type::stringstream_t m_stream; + std::string m_parentApplicationName; + bool m_isConfigured; + Configurations m_configurations; + std::unordered_map m_unflushedCount; + base::LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + LogBuilderPtr m_logBuilder; + + friend class el::LogMessage; + friend class el::Loggers; + friend class el::Helpers; + friend class el::base::RegisteredLoggers; + friend class el::base::DefaultLogDispatchCallback; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::PErrorWriter; + friend class el::base::Storage; + friend class el::base::PerformanceTracker; + friend class el::base::LogDispatcher; + + Logger(void); + +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED + template + void log_(Level, int, const char*, const T&, const Args&...); + + template + inline void log_(Level, int, const T&); + + template + void log(Level, const char*, const T&, const Args&...); + + template + inline void log(Level, const T&); +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED + + void initUnflushedCount(void); + + inline base::type::stringstream_t& stream(void) { + return m_stream; + } + + void resolveLoggerFormatSpec(void) const; +}; +namespace base { +/// @brief Loggers repository +class RegisteredLoggers : public base::utils::Registry { + public: + explicit RegisteredLoggers(const LogBuilderPtr& defaultLogBuilder); + + virtual ~RegisteredLoggers(void) { + unsafeFlushAll(); + } + + inline void setDefaultConfigurations(const Configurations& configurations) { + base::threading::ScopedLock scopedLock(lock()); + m_defaultConfigurations.setFromBase(const_cast(&configurations)); + } + + inline Configurations* defaultConfigurations(void) { + return &m_defaultConfigurations; + } + + Logger* get(const std::string& id, bool forceCreation = true); + + template + inline bool installLoggerRegistrationCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, + &m_loggerRegistrationCallbacks); + } + + template + inline void uninstallLoggerRegistrationCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, &m_loggerRegistrationCallbacks); + } + + template + inline T* loggerRegistrationCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_loggerRegistrationCallbacks); + } + + bool remove(const std::string& id); + + inline bool has(const std::string& id) { + return get(id, false) != nullptr; + } + + inline void unregister(Logger*& logger) { + base::threading::ScopedLock scopedLock(lock()); + base::utils::Registry::unregister(logger->id()); + } + + inline LogStreamsReferenceMapPtr logStreamsReference(void) { + return m_logStreamsReference; + } + + inline void flushAll(void) { + base::threading::ScopedLock scopedLock(lock()); + unsafeFlushAll(); + } + + inline void setDefaultLogBuilder(LogBuilderPtr& logBuilderPtr) { + base::threading::ScopedLock scopedLock(lock()); + m_defaultLogBuilder = logBuilderPtr; + } + + private: + LogBuilderPtr m_defaultLogBuilder; + Configurations m_defaultConfigurations; + base::LogStreamsReferenceMapPtr m_logStreamsReference = nullptr; + std::unordered_map m_loggerRegistrationCallbacks; + friend class el::base::Storage; + + void unsafeFlushAll(void); +}; +/// @brief Represents registries for verbose logging +class VRegistry : base::NoCopy, public base::threading::ThreadSafe { + public: + explicit VRegistry(base::type::VerboseLevel level, base::type::EnumType* pFlags); + + /// @brief Sets verbose level. Accepted range is 0-9 + void setLevel(base::type::VerboseLevel level); + + inline base::type::VerboseLevel level(void) const { + return m_level; + } + + inline void clearModules(void) { + base::threading::ScopedLock scopedLock(lock()); + m_modules.clear(); + } + + void setModules(const char* modules); + + bool allowed(base::type::VerboseLevel vlevel, const char* file); + + inline const std::unordered_map& modules(void) const { + return m_modules; + } + + void setFromArgs(const base::utils::CommandLineArgs* commandLineArgs); + + /// @brief Whether or not vModules enabled + inline bool vModulesEnabled(void) { + return !base::utils::hasFlag(LoggingFlag::DisableVModules, *m_pFlags); + } + + private: + base::type::VerboseLevel m_level; + base::type::EnumType* m_pFlags; + std::unordered_map m_modules; +}; +} // namespace base +class LogMessage { + public: + LogMessage(Level level, const std::string& file, base::type::LineNumber line, const std::string& func, + base::type::VerboseLevel verboseLevel, Logger* logger) : + m_level(level), m_file(file), m_line(line), m_func(func), + m_verboseLevel(verboseLevel), m_logger(logger), m_message(logger->stream().str()) { + } + inline Level level(void) const { + return m_level; + } + inline const std::string& file(void) const { + return m_file; + } + inline base::type::LineNumber line(void) const { + return m_line; + } + inline const std::string& func(void) const { + return m_func; + } + inline base::type::VerboseLevel verboseLevel(void) const { + return m_verboseLevel; + } + inline Logger* logger(void) const { + return m_logger; + } + inline const base::type::string_t& message(void) const { + return m_message; + } + private: + Level m_level; + std::string m_file; + base::type::LineNumber m_line; + std::string m_func; + base::type::VerboseLevel m_verboseLevel; + Logger* m_logger; + base::type::string_t m_message; +}; +namespace base { +#if ELPP_ASYNC_LOGGING +class AsyncLogItem { + public: + explicit AsyncLogItem(const LogMessage& logMessage, const LogDispatchData& data, const base::type::string_t& logLine) + : m_logMessage(logMessage), m_dispatchData(data), m_logLine(logLine) {} + virtual ~AsyncLogItem() {} + inline LogMessage* logMessage(void) { + return &m_logMessage; + } + inline LogDispatchData* data(void) { + return &m_dispatchData; + } + inline base::type::string_t logLine(void) { + return m_logLine; + } + private: + LogMessage m_logMessage; + LogDispatchData m_dispatchData; + base::type::string_t m_logLine; +}; +class AsyncLogQueue : public base::threading::ThreadSafe { + public: + virtual ~AsyncLogQueue() { + ELPP_INTERNAL_INFO(6, "~AsyncLogQueue"); + } + + inline AsyncLogItem next(void) { + base::threading::ScopedLock scopedLock(lock()); + AsyncLogItem result = m_queue.front(); + m_queue.pop(); + return result; + } + + inline void push(const AsyncLogItem& item) { + base::threading::ScopedLock scopedLock(lock()); + m_queue.push(item); + } + inline void pop(void) { + base::threading::ScopedLock scopedLock(lock()); + m_queue.pop(); + } + inline AsyncLogItem front(void) { + base::threading::ScopedLock scopedLock(lock()); + return m_queue.front(); + } + inline bool empty(void) { + base::threading::ScopedLock scopedLock(lock()); + return m_queue.empty(); + } + private: + std::queue m_queue; +}; +class IWorker { + public: + virtual ~IWorker() {} + virtual void start() = 0; +}; +#endif // ELPP_ASYNC_LOGGING +/// @brief Easylogging++ management storage +class Storage : base::NoCopy, public base::threading::ThreadSafe { + public: +#if ELPP_ASYNC_LOGGING + Storage(const LogBuilderPtr& defaultLogBuilder, base::IWorker* asyncDispatchWorker); +#else + explicit Storage(const LogBuilderPtr& defaultLogBuilder); +#endif // ELPP_ASYNC_LOGGING + + virtual ~Storage(void); + + inline bool validateEveryNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t occasion) { + return hitCounters()->validateEveryN(filename, lineNumber, occasion); + } + + inline bool validateAfterNCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + return hitCounters()->validateAfterN(filename, lineNumber, n); + } + + inline bool validateNTimesCounter(const char* filename, base::type::LineNumber lineNumber, std::size_t n) { + return hitCounters()->validateNTimes(filename, lineNumber, n); + } + + inline base::RegisteredHitCounters* hitCounters(void) const { + return m_registeredHitCounters; + } + + inline base::RegisteredLoggers* registeredLoggers(void) const { + return m_registeredLoggers; + } + + inline base::VRegistry* vRegistry(void) const { + return m_vRegistry; + } + +#if ELPP_ASYNC_LOGGING + inline base::AsyncLogQueue* asyncLogQueue(void) const { + return m_asyncLogQueue; + } +#endif // ELPP_ASYNC_LOGGING + + inline const base::utils::CommandLineArgs* commandLineArgs(void) const { + return &m_commandLineArgs; + } + + inline void addFlag(LoggingFlag flag) { + base::utils::addFlag(flag, &m_flags); + } + + inline void removeFlag(LoggingFlag flag) { + base::utils::removeFlag(flag, &m_flags); + } + + inline bool hasFlag(LoggingFlag flag) const { + return base::utils::hasFlag(flag, m_flags); + } + + inline base::type::EnumType flags(void) const { + return m_flags; + } + + inline void setFlags(base::type::EnumType flags) { + m_flags = flags; + } + + inline void setPreRollOutCallback(const PreRollOutCallback& callback) { + m_preRollOutCallback = callback; + } + + inline void unsetPreRollOutCallback(void) { + m_preRollOutCallback = base::defaultPreRollOutCallback; + } + + inline PreRollOutCallback& preRollOutCallback(void) { + return m_preRollOutCallback; + } + + bool hasCustomFormatSpecifier(const char* formatSpecifier); + void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier); + bool uninstallCustomFormatSpecifier(const char* formatSpecifier); + + const std::vector* customFormatSpecifiers(void) const { + return &m_customFormatSpecifiers; + } + + base::threading::Mutex& customFormatSpecifiersLock() { + return m_customFormatSpecifiersLock; + } + + inline void setLoggingLevel(Level level) { + m_loggingLevel = level; + } + + template + inline bool installLogDispatchCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, &m_logDispatchCallbacks); + } + + template + inline void uninstallLogDispatchCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, &m_logDispatchCallbacks); + } + template + inline T* logDispatchCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_logDispatchCallbacks); + } + +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + template + inline bool installPerformanceTrackingCallback(const std::string& id) { + return base::utils::Utils::installCallback(id, + &m_performanceTrackingCallbacks); + } + + template + inline void uninstallPerformanceTrackingCallback(const std::string& id) { + base::utils::Utils::uninstallCallback(id, + &m_performanceTrackingCallbacks); + } + + template + inline T* performanceTrackingCallback(const std::string& id) { + return base::utils::Utils::callback(id, &m_performanceTrackingCallbacks); + } +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + + /// @brief Sets thread name for current thread. Requires std::thread + inline void setThreadName(const std::string& name) { + if (name.empty()) return; + base::threading::ScopedLock scopedLock(m_threadNamesLock); + m_threadNames[base::threading::getCurrentThreadId()] = name; + } + + inline std::string getThreadName(const std::string& threadId) { + base::threading::ScopedLock scopedLock(m_threadNamesLock); + std::unordered_map::const_iterator it = m_threadNames.find(threadId); + if (it == m_threadNames.end()) { + return threadId; + } + return it->second; + } + private: + base::RegisteredHitCounters* m_registeredHitCounters; + base::RegisteredLoggers* m_registeredLoggers; + base::type::EnumType m_flags; + base::VRegistry* m_vRegistry; +#if ELPP_ASYNC_LOGGING + base::AsyncLogQueue* m_asyncLogQueue; + base::IWorker* m_asyncDispatchWorker; +#endif // ELPP_ASYNC_LOGGING + base::utils::CommandLineArgs m_commandLineArgs; + PreRollOutCallback m_preRollOutCallback; + std::unordered_map m_logDispatchCallbacks; + std::unordered_map m_performanceTrackingCallbacks; + std::unordered_map m_threadNames; + std::vector m_customFormatSpecifiers; + base::threading::Mutex m_customFormatSpecifiersLock; + base::threading::Mutex m_threadNamesLock; + Level m_loggingLevel; + + friend class el::Helpers; + friend class el::base::DefaultLogDispatchCallback; + friend class el::LogBuilder; + friend class el::base::MessageBuilder; + friend class el::base::Writer; + friend class el::base::PerformanceTracker; + friend class el::base::LogDispatcher; + + void setApplicationArguments(int argc, char** argv); + + inline void setApplicationArguments(int argc, const char** argv) { + setApplicationArguments(argc, const_cast(argv)); + } +}; +extern ELPP_EXPORT base::type::StoragePointer elStorage; +#define ELPP el::base::elStorage +class DefaultLogDispatchCallback : public LogDispatchCallback { + protected: + void handle(const LogDispatchData* data); + private: + const LogDispatchData* m_data; + void dispatch(base::type::string_t&& logLine); +}; +#if ELPP_ASYNC_LOGGING +class AsyncLogDispatchCallback : public LogDispatchCallback { + protected: + void handle(const LogDispatchData* data); +}; +class AsyncDispatchWorker : public base::IWorker, public base::threading::ThreadSafe { + public: + AsyncDispatchWorker(); + virtual ~AsyncDispatchWorker(); + + bool clean(void); + void emptyQueue(void); + virtual void start(void); + void handle(AsyncLogItem* logItem); + void run(void); + + void setContinueRunning(bool value) { + base::threading::ScopedLock scopedLock(m_continueRunningLock); + m_continueRunning = value; + } + + bool continueRunning(void) const { + return m_continueRunning; + } + private: + std::condition_variable cv; + bool m_continueRunning; + base::threading::Mutex m_continueRunningLock; +}; +#endif // ELPP_ASYNC_LOGGING +} // namespace base +namespace base { +class DefaultLogBuilder : public LogBuilder { + public: + base::type::string_t build(const LogMessage* logMessage, bool appendNewLine) const; +}; +/// @brief Dispatches log messages +class LogDispatcher : base::NoCopy { + public: + LogDispatcher(bool proceed, LogMessage* logMessage, base::DispatchAction dispatchAction) : + m_proceed(proceed), + m_logMessage(logMessage), + m_dispatchAction(std::move(dispatchAction)) { + } + + void dispatch(void); + + private: + bool m_proceed; + LogMessage* m_logMessage; + base::DispatchAction m_dispatchAction; +}; +#if defined(ELPP_STL_LOGGING) +/// @brief Workarounds to write some STL logs +/// +/// @detail There is workaround needed to loop through some stl containers. In order to do that, we need iterable containers +/// of same type and provide iterator interface and pass it on to writeIterator(). +/// Remember, this is passed by value in constructor so that we dont change original containers. +/// This operation is as expensive as Big-O(std::min(class_.size(), base::consts::kMaxLogPerContainer)) +namespace workarounds { +/// @brief Abstract IterableContainer template that provides interface for iterable classes of type T +template +class IterableContainer { + public: + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + IterableContainer(void) {} + virtual ~IterableContainer(void) {} + iterator begin(void) { + return getContainer().begin(); + } + iterator end(void) { + return getContainer().end(); + } + private: + virtual Container& getContainer(void) = 0; +}; +/// @brief Implements IterableContainer and provides iterable std::priority_queue class +template, typename Comparator = std::less> +class IterablePriorityQueue : public IterableContainer, + public std::priority_queue { + public: + IterablePriorityQueue(std::priority_queue queue_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { + this->push(queue_.top()); + queue_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +/// @brief Implements IterableContainer and provides iterable std::queue class +template> +class IterableQueue : public IterableContainer, public std::queue { + public: + IterableQueue(std::queue queue_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !queue_.empty()) { + this->push(queue_.front()); + queue_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +/// @brief Implements IterableContainer and provides iterable std::stack class +template> +class IterableStack : public IterableContainer, public std::stack { + public: + IterableStack(std::stack stack_) { + std::size_t count_ = 0; + while (++count_ < base::consts::kMaxLogPerContainer && !stack_.empty()) { + this->push(stack_.top()); + stack_.pop(); + } + } + private: + inline Container& getContainer(void) { + return this->c; + } +}; +} // namespace workarounds +#endif // defined(ELPP_STL_LOGGING) +// Log message builder +class MessageBuilder { + public: + MessageBuilder(void) : m_logger(nullptr), m_containerLogSeparator(ELPP_LITERAL("")) {} + void initialize(Logger* logger); + +# define ELPP_SIMPLE_LOG(LOG_TYPE)\ +MessageBuilder& operator<<(LOG_TYPE msg) {\ +m_logger->stream() << msg;\ +if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) {\ +m_logger->stream() << " ";\ +}\ +return *this;\ +} + + inline MessageBuilder& operator<<(const std::string& msg) { + return operator<<(msg.c_str()); + } + ELPP_SIMPLE_LOG(char) + ELPP_SIMPLE_LOG(bool) + ELPP_SIMPLE_LOG(signed short) + ELPP_SIMPLE_LOG(unsigned short) + ELPP_SIMPLE_LOG(signed int) + ELPP_SIMPLE_LOG(unsigned int) + ELPP_SIMPLE_LOG(signed long) + ELPP_SIMPLE_LOG(unsigned long) + ELPP_SIMPLE_LOG(float) + ELPP_SIMPLE_LOG(double) + ELPP_SIMPLE_LOG(char*) + ELPP_SIMPLE_LOG(const char*) + ELPP_SIMPLE_LOG(const void*) + ELPP_SIMPLE_LOG(long double) + inline MessageBuilder& operator<<(const std::wstring& msg) { + return operator<<(msg.c_str()); + } + MessageBuilder& operator<<(const wchar_t* msg); + // ostream manipulators + inline MessageBuilder& operator<<(std::ostream& (*OStreamMani)(std::ostream&)) { + m_logger->stream() << OStreamMani; + return *this; + } +#define ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} +#define ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(temp) \ +template \ +inline MessageBuilder& operator<<(const temp& template_inst) { \ +return writeIterator(template_inst.begin(), template_inst.end(), template_inst.size()); \ +} + +#if defined(ELPP_STL_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::list) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(std::deque) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::set) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(std::multiset) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::map) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::multimap) + template + inline MessageBuilder& operator<<(const std::queue& queue_) { + base::workarounds::IterableQueue iterableQueue_ = + static_cast >(queue_); + return writeIterator(iterableQueue_.begin(), iterableQueue_.end(), iterableQueue_.size()); + } + template + inline MessageBuilder& operator<<(const std::stack& stack_) { + base::workarounds::IterableStack iterableStack_ = + static_cast >(stack_); + return writeIterator(iterableStack_.begin(), iterableStack_.end(), iterableStack_.size()); + } + template + inline MessageBuilder& operator<<(const std::priority_queue& priorityQueue_) { + base::workarounds::IterablePriorityQueue iterablePriorityQueue_ = + static_cast >(priorityQueue_); + return writeIterator(iterablePriorityQueue_.begin(), iterablePriorityQueue_.end(), iterablePriorityQueue_.size()); + } + template + MessageBuilder& operator<<(const std::pair& pair_) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(pair_.first)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(pair_.second)); + m_logger->stream() << ELPP_LITERAL(")"); + return *this; + } + template + MessageBuilder& operator<<(const std::bitset& bitset_) { + m_logger->stream() << ELPP_LITERAL("["); + operator << (bitset_.to_string()); + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } +# if defined(ELPP_LOG_STD_ARRAY) + template + inline MessageBuilder& operator<<(const std::array& array) { + return writeIterator(array.begin(), array.end(), array.size()); + } +# endif // defined(ELPP_LOG_STD_ARRAY) +# if defined(ELPP_LOG_UNORDERED_MAP) + ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_map) + ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG(std::unordered_multimap) +# endif // defined(ELPP_LOG_UNORDERED_MAP) +# if defined(ELPP_LOG_UNORDERED_SET) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_set) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(std::unordered_multiset) +# endif // defined(ELPP_LOG_UNORDERED_SET) +#endif // defined(ELPP_STL_LOGGING) +#if defined(ELPP_QT_LOGGING) + inline MessageBuilder& operator<<(const QString& msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << msg.toStdWString(); +# else + m_logger->stream() << msg.toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(const QByteArray& msg) { + return operator << (QString(msg)); + } + inline MessageBuilder& operator<<(const QStringRef& msg) { + return operator<<(msg.toString()); + } + inline MessageBuilder& operator<<(qint64 msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << QString::number(msg).toStdWString(); +# else + m_logger->stream() << QString::number(msg).toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(quint64 msg) { +# if defined(ELPP_UNICODE) + m_logger->stream() << QString::number(msg).toStdWString(); +# else + m_logger->stream() << QString::number(msg).toStdString(); +# endif // defined(ELPP_UNICODE) + return *this; + } + inline MessageBuilder& operator<<(QChar msg) { + m_logger->stream() << msg.toLatin1(); + return *this; + } + inline MessageBuilder& operator<<(const QLatin1String& msg) { + m_logger->stream() << msg.latin1(); + return *this; + } + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QList) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QVector) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QQueue) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QSet) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QLinkedList) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(QStack) + template + MessageBuilder& operator<<(const QPair& pair_) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(pair_.first)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(pair_.second)); + m_logger->stream() << ELPP_LITERAL(")"); + return *this; + } + template + MessageBuilder& operator<<(const QMap& map_) { + m_logger->stream() << ELPP_LITERAL("["); + QList keys = map_.keys(); + typename QList::const_iterator begin = keys.begin(); + typename QList::const_iterator end = keys.end(); + int max_ = static_cast(base::consts::kMaxLogPerContainer); // to prevent warning + for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(*begin)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(map_.value(*begin))); + m_logger->stream() << ELPP_LITERAL(")"); + m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin != end) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } + template + inline MessageBuilder& operator<<(const QMultiMap& map_) { + operator << (static_cast>(map_)); + return *this; + } + template + MessageBuilder& operator<<(const QHash& hash_) { + m_logger->stream() << ELPP_LITERAL("["); + QList keys = hash_.keys(); + typename QList::const_iterator begin = keys.begin(); + typename QList::const_iterator end = keys.end(); + int max_ = static_cast(base::consts::kMaxLogPerContainer); // prevent type warning + for (int index_ = 0; begin != end && index_ < max_; ++index_, ++begin) { + m_logger->stream() << ELPP_LITERAL("("); + operator << (static_cast(*begin)); + m_logger->stream() << ELPP_LITERAL(", "); + operator << (static_cast(hash_.value(*begin))); + m_logger->stream() << ELPP_LITERAL(")"); + m_logger->stream() << ((index_ < keys.size() -1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin != end) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + return *this; + } + template + inline MessageBuilder& operator<<(const QMultiHash& multiHash_) { + operator << (static_cast>(multiHash_)); + return *this; + } +#endif // defined(ELPP_QT_LOGGING) +#if defined(ELPP_BOOST_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::stable_vector) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::list) + ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG(boost::container::deque) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::map) + ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG(boost::container::flat_map) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::set) + ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG(boost::container::flat_set) +#endif // defined(ELPP_BOOST_LOGGING) + + /// @brief Macro used internally that can be used externally to make containers easylogging++ friendly + /// + /// @detail This macro expands to write an ostream& operator<< for container. This container is expected to + /// have begin() and end() methods that return respective iterators + /// @param ContainerType Type of container e.g, MyList from WX_DECLARE_LIST(int, MyList); in wxwidgets + /// @param SizeMethod Method used to get size of container. + /// @param ElementInstance Instance of element to be fed out. Instance name is "elem". See WXELPP_ENABLED macro + /// for an example usage +#define MAKE_CONTAINERELPP_FRIENDLY(ContainerType, SizeMethod, ElementInstance) \ +el::base::type::ostream_t& operator<<(el::base::type::ostream_t& ss, const ContainerType& container) {\ +const el::base::type::char_t* sep = ELPP->hasFlag(el::LoggingFlag::NewLineForContainer) ? \ +ELPP_LITERAL("\n ") : ELPP_LITERAL(", ");\ +ContainerType::const_iterator elem = container.begin();\ +ContainerType::const_iterator endElem = container.end();\ +std::size_t size_ = container.SizeMethod; \ +ss << ELPP_LITERAL("[");\ +for (std::size_t i = 0; elem != endElem && i < el::base::consts::kMaxLogPerContainer; ++i, ++elem) { \ +ss << ElementInstance;\ +ss << ((i < size_ - 1) ? sep : ELPP_LITERAL(""));\ +}\ +if (elem != endElem) {\ +ss << ELPP_LITERAL("...");\ +}\ +ss << ELPP_LITERAL("]");\ +return ss;\ +} +#if defined(ELPP_WXWIDGETS_LOGGING) + ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG(wxVector) +# define ELPP_WX_PTR_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), *(*elem)) +# define ELPP_WX_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), (*elem)) +# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) MAKE_CONTAINERELPP_FRIENDLY(ContainerType, size(), \ +ELPP_LITERAL("(") << elem->first << ELPP_LITERAL(", ") << elem->second << ELPP_LITERAL(")") +#else +# define ELPP_WX_PTR_ENABLED(ContainerType) +# define ELPP_WX_ENABLED(ContainerType) +# define ELPP_WX_HASH_MAP_ENABLED(ContainerType) +#endif // defined(ELPP_WXWIDGETS_LOGGING) + // Other classes + template + ELPP_SIMPLE_LOG(const Class&) +#undef ELPP_SIMPLE_LOG +#undef ELPP_ITERATOR_CONTAINER_LOG_ONE_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_TWO_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_THREE_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_FOUR_ARG +#undef ELPP_ITERATOR_CONTAINER_LOG_FIVE_ARG + private: + Logger* m_logger; + const base::type::char_t* m_containerLogSeparator; + + template + MessageBuilder& writeIterator(Iterator begin_, Iterator end_, std::size_t size_) { + m_logger->stream() << ELPP_LITERAL("["); + for (std::size_t i = 0; begin_ != end_ && i < base::consts::kMaxLogPerContainer; ++i, ++begin_) { + operator << (*begin_); + m_logger->stream() << ((i < size_ - 1) ? m_containerLogSeparator : ELPP_LITERAL("")); + } + if (begin_ != end_) { + m_logger->stream() << ELPP_LITERAL("..."); + } + m_logger->stream() << ELPP_LITERAL("]"); + if (ELPP->hasFlag(LoggingFlag::AutoSpacing)) { + m_logger->stream() << " "; + } + return *this; + } +}; +/// @brief Writes nothing - Used when certain log is disabled +class NullWriter : base::NoCopy { + public: + NullWriter(void) {} + + // Null manipulator + inline NullWriter& operator<<(std::ostream& (*)(std::ostream&)) { + return *this; + } + + template + inline NullWriter& operator<<(const T&) { + return *this; + } + + inline operator bool() { + return true; + } +}; +/// @brief Main entry point of each logging +class Writer : base::NoCopy { + public: + Writer(Level level, const char* file, base::type::LineNumber line, + const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, + base::type::VerboseLevel verboseLevel = 0) : + m_msg(nullptr), m_level(level), m_file(file), m_line(line), m_func(func), m_verboseLevel(verboseLevel), + m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { + } + + Writer(LogMessage* msg, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog) : + m_msg(msg), m_level(msg != nullptr ? msg->level() : Level::Unknown), + m_line(0), m_logger(nullptr), m_proceed(false), m_dispatchAction(dispatchAction) { + } + + virtual ~Writer(void) { + processDispatch(); + } + + template + inline Writer& operator<<(const T& log) { +#if ELPP_LOGGING_ENABLED + if (m_proceed) { + m_messageBuilder << log; + } +#endif // ELPP_LOGGING_ENABLED + return *this; + } + + inline Writer& operator<<(std::ostream& (*log)(std::ostream&)) { +#if ELPP_LOGGING_ENABLED + if (m_proceed) { + m_messageBuilder << log; + } +#endif // ELPP_LOGGING_ENABLED + return *this; + } + + inline operator bool() { + return true; + } + + Writer& construct(Logger* logger, bool needLock = true); + Writer& construct(int count, const char* loggerIds, ...); + protected: + LogMessage* m_msg; + Level m_level; + const char* m_file; + const base::type::LineNumber m_line; + const char* m_func; + base::type::VerboseLevel m_verboseLevel; + Logger* m_logger; + bool m_proceed; + base::MessageBuilder m_messageBuilder; + base::DispatchAction m_dispatchAction; + std::vector m_loggerIds; + friend class el::Helpers; + + void initializeLogger(const std::string& loggerId, bool lookup = true, bool needLock = true); + void processDispatch(); + void triggerDispatch(void); +}; +class PErrorWriter : public base::Writer { + public: + PErrorWriter(Level level, const char* file, base::type::LineNumber line, + const char* func, base::DispatchAction dispatchAction = base::DispatchAction::NormalLog, + base::type::VerboseLevel verboseLevel = 0) : + base::Writer(level, file, line, func, dispatchAction, verboseLevel) { + } + + virtual ~PErrorWriter(void); +}; +} // namespace base +// Logging from Logger class. Why this is here? Because we have Storage and Writer class available +#if ELPP_VARIADIC_TEMPLATES_SUPPORTED +template +void Logger::log_(Level level, int vlevel, const char* s, const T& value, const Args&... args) { + base::MessageBuilder b; + b.initialize(this); + while (*s) { + if (*s == base::consts::kFormatSpecifierChar) { + if (*(s + 1) == base::consts::kFormatSpecifierChar) { + ++s; + } else { + if (*(s + 1) == base::consts::kFormatSpecifierCharValue) { + ++s; + b << value; + log_(level, vlevel, ++s, args...); + return; + } + } + } + b << *s++; + } + ELPP_INTERNAL_ERROR("Too many arguments provided. Unable to handle. Please provide more format specifiers", false); +} +template +void Logger::log_(Level level, int vlevel, const T& log) { + if (level == Level::Verbose) { + if (ELPP->vRegistry()->allowed(vlevel, __FILE__)) { + base::Writer(Level::Verbose, "FILE", 0, "FUNCTION", + base::DispatchAction::NormalLog, vlevel).construct(this, false) << log; + } else { + stream().str(ELPP_LITERAL("")); + releaseLock(); + } + } else { + base::Writer(level, "FILE", 0, "FUNCTION").construct(this, false) << log; + } +} +template +inline void Logger::log(Level level, const char* s, const T& value, const Args&... args) { + acquireLock(); // released in Writer! + log_(level, 0, s, value, args...); +} +template +inline void Logger::log(Level level, const T& log) { + acquireLock(); // released in Writer! + log_(level, 0, log); +} +# if ELPP_VERBOSE_LOG +template +inline void Logger::verbose(int vlevel, const char* s, const T& value, const Args&... args) { + acquireLock(); // released in Writer! + log_(el::Level::Verbose, vlevel, s, value, args...); +} +template +inline void Logger::verbose(int vlevel, const T& log) { + acquireLock(); // released in Writer! + log_(el::Level::Verbose, vlevel, log); +} +# else +template +inline void Logger::verbose(int, const char*, const T&, const Args&...) { + return; +} +template +inline void Logger::verbose(int, const T&) { + return; +} +# endif // ELPP_VERBOSE_LOG +# define LOGGER_LEVEL_WRITERS(FUNCTION_NAME, LOG_LEVEL)\ +template \ +inline void Logger::FUNCTION_NAME(const char* s, const T& value, const Args&... args) {\ +log(LOG_LEVEL, s, value, args...);\ +}\ +template \ +inline void Logger::FUNCTION_NAME(const T& value) {\ +log(LOG_LEVEL, value);\ +} +# define LOGGER_LEVEL_WRITERS_DISABLED(FUNCTION_NAME, LOG_LEVEL)\ +template \ +inline void Logger::FUNCTION_NAME(const char*, const T&, const Args&...) {\ +return;\ +}\ +template \ +inline void Logger::FUNCTION_NAME(const T&) {\ +return;\ +} + +# if ELPP_INFO_LOG +LOGGER_LEVEL_WRITERS(info, Level::Info) +# else +LOGGER_LEVEL_WRITERS_DISABLED(info, Level::Info) +# endif // ELPP_INFO_LOG +# if ELPP_DEBUG_LOG +LOGGER_LEVEL_WRITERS(debug, Level::Debug) +# else +LOGGER_LEVEL_WRITERS_DISABLED(debug, Level::Debug) +# endif // ELPP_DEBUG_LOG +# if ELPP_WARNING_LOG +LOGGER_LEVEL_WRITERS(warn, Level::Warning) +# else +LOGGER_LEVEL_WRITERS_DISABLED(warn, Level::Warning) +# endif // ELPP_WARNING_LOG +# if ELPP_ERROR_LOG +LOGGER_LEVEL_WRITERS(error, Level::Error) +# else +LOGGER_LEVEL_WRITERS_DISABLED(error, Level::Error) +# endif // ELPP_ERROR_LOG +# if ELPP_FATAL_LOG +LOGGER_LEVEL_WRITERS(fatal, Level::Fatal) +# else +LOGGER_LEVEL_WRITERS_DISABLED(fatal, Level::Fatal) +# endif // ELPP_FATAL_LOG +# if ELPP_TRACE_LOG +LOGGER_LEVEL_WRITERS(trace, Level::Trace) +# else +LOGGER_LEVEL_WRITERS_DISABLED(trace, Level::Trace) +# endif // ELPP_TRACE_LOG +# undef LOGGER_LEVEL_WRITERS +# undef LOGGER_LEVEL_WRITERS_DISABLED +#endif // ELPP_VARIADIC_TEMPLATES_SUPPORTED +#if ELPP_COMPILER_MSVC +# define ELPP_VARIADIC_FUNC_MSVC(variadicFunction, variadicArgs) variadicFunction variadicArgs +# define ELPP_VARIADIC_FUNC_MSVC_RUN(variadicFunction, ...) ELPP_VARIADIC_FUNC_MSVC(variadicFunction, (__VA_ARGS__)) +# define el_getVALength(...) ELPP_VARIADIC_FUNC_MSVC_RUN(el_resolveVALength, 0, ## __VA_ARGS__,\ +10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#else +# if ELPP_COMPILER_CLANG +# define el_getVALength(...) el_resolveVALength(0, __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +# else +# define el_getVALength(...) el_resolveVALength(0, ## __VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +# endif // ELPP_COMPILER_CLANG +#endif // ELPP_COMPILER_MSVC +#define el_resolveVALength(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define ELPP_WRITE_LOG(writer, level, dispatchAction, ...) \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_IF(writer, condition, level, dispatchAction, ...) if (condition) \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_EVERY_N(writer, occasion, level, dispatchAction, ...) \ +ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_AFTER_N(writer, n, level, dispatchAction, ...) \ +ELPP->validateAfterNCounter(__FILE__, __LINE__, n) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#define ELPP_WRITE_LOG_N_TIMES(writer, n, level, dispatchAction, ...) \ +ELPP->validateNTimesCounter(__FILE__, __LINE__, n) && \ +writer(level, __FILE__, __LINE__, ELPP_FUNC, dispatchAction).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +class PerformanceTrackingData { + public: + enum class DataType : base::type::EnumType { + Checkpoint = 1, Complete = 2 + }; + // Do not use constructor, will run into multiple definition error, use init(PerformanceTracker*) + explicit PerformanceTrackingData(DataType dataType) : m_performanceTracker(nullptr), + m_dataType(dataType), m_firstCheckpoint(false), m_file(""), m_line(0), m_func("") {} + inline const std::string* blockName(void) const; + inline const struct timeval* startTime(void) const; + inline const struct timeval* endTime(void) const; + inline const struct timeval* lastCheckpointTime(void) const; + inline const base::PerformanceTracker* performanceTracker(void) const { + return m_performanceTracker; + } + inline PerformanceTrackingData::DataType dataType(void) const { + return m_dataType; + } + inline bool firstCheckpoint(void) const { + return m_firstCheckpoint; + } + inline std::string checkpointId(void) const { + return m_checkpointId; + } + inline const char* file(void) const { + return m_file; + } + inline base::type::LineNumber line(void) const { + return m_line; + } + inline const char* func(void) const { + return m_func; + } + inline const base::type::string_t* formattedTimeTaken() const { + return &m_formattedTimeTaken; + } + inline const std::string& loggerId(void) const; + private: + base::PerformanceTracker* m_performanceTracker; + base::type::string_t m_formattedTimeTaken; + PerformanceTrackingData::DataType m_dataType; + bool m_firstCheckpoint; + std::string m_checkpointId; + const char* m_file; + base::type::LineNumber m_line; + const char* m_func; + inline void init(base::PerformanceTracker* performanceTracker, bool firstCheckpoint = false) { + m_performanceTracker = performanceTracker; + m_firstCheckpoint = firstCheckpoint; + } + + friend class el::base::PerformanceTracker; +}; +namespace base { +/// @brief Represents performanceTracker block of code that conditionally adds performance status to log +/// either when goes outside the scope of when checkpoint() is called +class PerformanceTracker : public base::threading::ThreadSafe, public Loggable { + public: + PerformanceTracker(const std::string& blockName, + base::TimestampUnit timestampUnit = base::TimestampUnit::Millisecond, + const std::string& loggerId = std::string(el::base::consts::kPerformanceLoggerId), + bool scopedLog = true, Level level = base::consts::kPerformanceTrackerDefaultLevel); + /// @brief Copy constructor + PerformanceTracker(const PerformanceTracker& t) : + m_blockName(t.m_blockName), m_timestampUnit(t.m_timestampUnit), m_loggerId(t.m_loggerId), m_scopedLog(t.m_scopedLog), + m_level(t.m_level), m_hasChecked(t.m_hasChecked), m_lastCheckpointId(t.m_lastCheckpointId), m_enabled(t.m_enabled), + m_startTime(t.m_startTime), m_endTime(t.m_endTime), m_lastCheckpointTime(t.m_lastCheckpointTime) { + } + virtual ~PerformanceTracker(void); + /// @brief A checkpoint for current performanceTracker block. + void checkpoint(const std::string& id = std::string(), const char* file = __FILE__, + base::type::LineNumber line = __LINE__, + const char* func = ""); + inline Level level(void) const { + return m_level; + } + private: + std::string m_blockName; + base::TimestampUnit m_timestampUnit; + std::string m_loggerId; + bool m_scopedLog; + Level m_level; + bool m_hasChecked; + std::string m_lastCheckpointId; + bool m_enabled; + struct timeval m_startTime, m_endTime, m_lastCheckpointTime; + + PerformanceTracker(void); + + friend class el::PerformanceTrackingData; + friend class base::DefaultPerformanceTrackingCallback; + + const inline base::type::string_t getFormattedTimeTaken() const { + return getFormattedTimeTaken(m_startTime); + } + + const base::type::string_t getFormattedTimeTaken(struct timeval startTime) const; + + virtual inline void log(el::base::type::ostream_t& os) const { + os << getFormattedTimeTaken(); + } +}; +class DefaultPerformanceTrackingCallback : public PerformanceTrackingCallback { + protected: + void handle(const PerformanceTrackingData* data) { + m_data = data; + base::type::stringstream_t ss; + if (m_data->dataType() == PerformanceTrackingData::DataType::Complete) { + ss << ELPP_LITERAL("Executed [") << m_data->blockName()->c_str() << ELPP_LITERAL("] in [") << + *m_data->formattedTimeTaken() << ELPP_LITERAL("]"); + } else { + ss << ELPP_LITERAL("Performance checkpoint"); + if (!m_data->checkpointId().empty()) { + ss << ELPP_LITERAL(" [") << m_data->checkpointId().c_str() << ELPP_LITERAL("]"); + } + ss << ELPP_LITERAL(" for block [") << m_data->blockName()->c_str() << ELPP_LITERAL("] : [") << + *m_data->performanceTracker(); + if (!ELPP->hasFlag(LoggingFlag::DisablePerformanceTrackingCheckpointComparison) + && m_data->performanceTracker()->m_hasChecked) { + ss << ELPP_LITERAL(" ([") << *m_data->formattedTimeTaken() << ELPP_LITERAL("] from "); + if (m_data->performanceTracker()->m_lastCheckpointId.empty()) { + ss << ELPP_LITERAL("last checkpoint"); + } else { + ss << ELPP_LITERAL("checkpoint '") << m_data->performanceTracker()->m_lastCheckpointId.c_str() << ELPP_LITERAL("'"); + } + ss << ELPP_LITERAL(")]"); + } else { + ss << ELPP_LITERAL("]"); + } + } + el::base::Writer(m_data->performanceTracker()->level(), m_data->file(), m_data->line(), m_data->func()).construct(1, + m_data->loggerId().c_str()) << ss.str(); + } + private: + const PerformanceTrackingData* m_data; +}; +} // namespace base +inline const std::string* PerformanceTrackingData::blockName() const { + return const_cast(&m_performanceTracker->m_blockName); +} +inline const struct timeval* PerformanceTrackingData::startTime() const { + return const_cast(&m_performanceTracker->m_startTime); +} +inline const struct timeval* PerformanceTrackingData::endTime() const { + return const_cast(&m_performanceTracker->m_endTime); +} +inline const struct timeval* PerformanceTrackingData::lastCheckpointTime() const { + return const_cast(&m_performanceTracker->m_lastCheckpointTime); +} +inline const std::string& PerformanceTrackingData::loggerId(void) const { + return m_performanceTracker->m_loggerId; +} +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) +namespace base { +/// @brief Contains some internal debugging tools like crash handler and stack tracer +namespace debug { +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +class StackTrace : base::NoCopy { + public: + static const unsigned int kMaxStack = 64; + static const unsigned int kStackStart = 2; // We want to skip c'tor and StackTrace::generateNew() + class StackTraceEntry { + public: + StackTraceEntry(std::size_t index, const std::string& loc, const std::string& demang, const std::string& hex, + const std::string& addr); + StackTraceEntry(std::size_t index, const std::string& loc) : + m_index(index), + m_location(loc) { + } + std::size_t m_index; + std::string m_location; + std::string m_demangled; + std::string m_hex; + std::string m_addr; + friend std::ostream& operator<<(std::ostream& ss, const StackTraceEntry& si); + + private: + StackTraceEntry(void); + }; + + StackTrace(void) { + generateNew(); + } + + virtual ~StackTrace(void) { + } + + inline std::vector& getLatestStack(void) { + return m_stack; + } + + friend std::ostream& operator<<(std::ostream& os, const StackTrace& st); + + private: + std::vector m_stack; + + void generateNew(void); +}; +/// @brief Handles unexpected crashes +class CrashHandler : base::NoCopy { + public: + typedef void (*Handler)(int); + + explicit CrashHandler(bool useDefault); + explicit CrashHandler(const Handler& cHandler) { + setHandler(cHandler); + } + void setHandler(const Handler& cHandler); + + private: + Handler m_handler; +}; +#else +class CrashHandler { + public: + explicit CrashHandler(bool) {} +}; +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) +} // namespace debug +} // namespace base +extern base::debug::CrashHandler elCrashHandler; +#define MAKE_LOGGABLE(ClassType, ClassInstance, OutputStreamInstance) \ +el::base::type::ostream_t& operator<<(el::base::type::ostream_t& OutputStreamInstance, const ClassType& ClassInstance) +/// @brief Initializes syslog with process ID, options and facility. calls closelog() on d'tor +class SysLogInitializer { + public: + SysLogInitializer(const char* processIdent, int options = 0, int facility = 0) { +#if defined(ELPP_SYSLOG) + (void)base::consts::kSysLogLoggerId; + openlog(processIdent, options, facility); +#else + ELPP_UNUSED(processIdent); + ELPP_UNUSED(options); + ELPP_UNUSED(facility); +#endif // defined(ELPP_SYSLOG) + } + virtual ~SysLogInitializer(void) { +#if defined(ELPP_SYSLOG) + closelog(); +#endif // defined(ELPP_SYSLOG) + } +}; +#define ELPP_INITIALIZE_SYSLOG(id, opt, fac) el::SysLogInitializer elSyslogInit(id, opt, fac) +/// @brief Static helpers for developers +class Helpers : base::StaticClass { + public: + /// @brief Shares logging repository (base::Storage) + static inline void setStorage(base::type::StoragePointer storage) { + ELPP = storage; + } + /// @return Main storage repository + static inline base::type::StoragePointer storage() { + return ELPP; + } + /// @brief Sets application arguments and figures out whats active for logging and whats not. + static inline void setArgs(int argc, char** argv) { + ELPP->setApplicationArguments(argc, argv); + } + /// @copydoc setArgs(int argc, char** argv) + static inline void setArgs(int argc, const char** argv) { + ELPP->setApplicationArguments(argc, const_cast(argv)); + } + /// @brief Sets thread name for current thread. Requires std::thread + static inline void setThreadName(const std::string& name) { + ELPP->setThreadName(name); + } + static inline std::string getThreadName() { + return ELPP->getThreadName(base::threading::getCurrentThreadId()); + } +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + /// @brief Overrides default crash handler and installs custom handler. + /// @param crashHandler A functor with no return type that takes single int argument. + /// Handler is a typedef with specification: void (*Handler)(int) + static inline void setCrashHandler(const el::base::debug::CrashHandler::Handler& crashHandler) { + el::elCrashHandler.setHandler(crashHandler); + } + /// @brief Abort due to crash with signal in parameter + /// @param sig Crash signal + static void crashAbort(int sig, const char* sourceFile = "", unsigned int long line = 0); + /// @brief Logs reason of crash as per sig + /// @param sig Crash signal + /// @param stackTraceIfAvailable Includes stack trace if available + /// @param level Logging level + /// @param logger Logger to use for logging + static void logCrashReason(int sig, bool stackTraceIfAvailable = false, + Level level = Level::Fatal, const char* logger = base::consts::kDefaultLoggerId); +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_CRASH_LOG) + /// @brief Installs pre rollout callback, this callback is triggered when log file is about to be rolled out + /// (can be useful for backing up) + static inline void installPreRollOutCallback(const PreRollOutCallback& callback) { + ELPP->setPreRollOutCallback(callback); + } + /// @brief Uninstalls pre rollout callback + static inline void uninstallPreRollOutCallback(void) { + ELPP->unsetPreRollOutCallback(); + } + /// @brief Installs post log dispatch callback, this callback is triggered when log is dispatched + template + static inline bool installLogDispatchCallback(const std::string& id) { + return ELPP->installLogDispatchCallback(id); + } + /// @brief Uninstalls log dispatch callback + template + static inline void uninstallLogDispatchCallback(const std::string& id) { + ELPP->uninstallLogDispatchCallback(id); + } + template + static inline T* logDispatchCallback(const std::string& id) { + return ELPP->logDispatchCallback(id); + } +#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + /// @brief Installs post performance tracking callback, this callback is triggered when performance tracking is finished + template + static inline bool installPerformanceTrackingCallback(const std::string& id) { + return ELPP->installPerformanceTrackingCallback(id); + } + /// @brief Uninstalls post performance tracking handler + template + static inline void uninstallPerformanceTrackingCallback(const std::string& id) { + ELPP->uninstallPerformanceTrackingCallback(id); + } + template + static inline T* performanceTrackingCallback(const std::string& id) { + return ELPP->performanceTrackingCallback(id); + } +#endif // defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING) + /// @brief Converts template to std::string - useful for loggable classes to log containers within log(std::ostream&) const + template + static std::string convertTemplateToStdString(const T& templ) { + el::Logger* logger = + ELPP->registeredLoggers()->get(el::base::consts::kDefaultLoggerId); + if (logger == nullptr) { + return std::string(); + } + base::MessageBuilder b; + b.initialize(logger); + logger->acquireLock(); + b << templ; +#if defined(ELPP_UNICODE) + std::string s = std::string(logger->stream().str().begin(), logger->stream().str().end()); +#else + std::string s = logger->stream().str(); +#endif // defined(ELPP_UNICODE) + logger->stream().str(ELPP_LITERAL("")); + logger->releaseLock(); + return s; + } + /// @brief Returns command line arguments (pointer) provided to easylogging++ + static inline const el::base::utils::CommandLineArgs* commandLineArgs(void) { + return ELPP->commandLineArgs(); + } + /// @brief Reserve space for custom format specifiers for performance + /// @see std::vector::reserve + static inline void reserveCustomFormatSpecifiers(std::size_t size) { + ELPP->m_customFormatSpecifiers.reserve(size); + } + /// @brief Installs user defined format specifier and handler + static inline void installCustomFormatSpecifier(const CustomFormatSpecifier& customFormatSpecifier) { + ELPP->installCustomFormatSpecifier(customFormatSpecifier); + } + /// @brief Uninstalls user defined format specifier and handler + static inline bool uninstallCustomFormatSpecifier(const char* formatSpecifier) { + return ELPP->uninstallCustomFormatSpecifier(formatSpecifier); + } + /// @brief Returns true if custom format specifier is installed + static inline bool hasCustomFormatSpecifier(const char* formatSpecifier) { + return ELPP->hasCustomFormatSpecifier(formatSpecifier); + } + static inline void validateFileRolling(Logger* logger, Level level) { + if (ELPP == nullptr || logger == nullptr) return; + logger->m_typedConfigurations->validateFileRolling(level, ELPP->preRollOutCallback()); + } +}; +/// @brief Static helpers to deal with loggers and their configurations +class Loggers : base::StaticClass { + public: + /// @brief Gets existing or registers new logger + static Logger* getLogger(const std::string& identity, bool registerIfNotAvailable = true); + /// @brief Changes default log builder for future loggers + static void setDefaultLogBuilder(el::LogBuilderPtr& logBuilderPtr); + /// @brief Installs logger registration callback, this callback is triggered when new logger is registered + template + static inline bool installLoggerRegistrationCallback(const std::string& id) { + return ELPP->registeredLoggers()->installLoggerRegistrationCallback(id); + } + /// @brief Uninstalls log dispatch callback + template + static inline void uninstallLoggerRegistrationCallback(const std::string& id) { + ELPP->registeredLoggers()->uninstallLoggerRegistrationCallback(id); + } + template + static inline T* loggerRegistrationCallback(const std::string& id) { + return ELPP->registeredLoggers()->loggerRegistrationCallback(id); + } + /// @brief Unregisters logger - use it only when you know what you are doing, you may unregister + /// loggers initialized / used by third-party libs. + static bool unregisterLogger(const std::string& identity); + /// @brief Whether or not logger with id is registered + static bool hasLogger(const std::string& identity); + /// @brief Reconfigures specified logger with new configurations + static Logger* reconfigureLogger(Logger* logger, const Configurations& configurations); + /// @brief Reconfigures logger with new configurations after looking it up using identity + static Logger* reconfigureLogger(const std::string& identity, const Configurations& configurations); + /// @brief Reconfigures logger's single configuration + static Logger* reconfigureLogger(const std::string& identity, ConfigurationType configurationType, + const std::string& value); + /// @brief Reconfigures all the existing loggers with new configurations + static void reconfigureAllLoggers(const Configurations& configurations); + /// @brief Reconfigures single configuration for all the loggers + static inline void reconfigureAllLoggers(ConfigurationType configurationType, const std::string& value) { + reconfigureAllLoggers(Level::Global, configurationType, value); + } + /// @brief Reconfigures single configuration for all the loggers for specified level + static void reconfigureAllLoggers(Level level, ConfigurationType configurationType, + const std::string& value); + /// @brief Sets default configurations. This configuration is used for future (and conditionally for existing) loggers + static void setDefaultConfigurations(const Configurations& configurations, + bool reconfigureExistingLoggers = false); + /// @brief Returns current default + static const Configurations* defaultConfigurations(void); + /// @brief Returns log stream reference pointer if needed by user + static const base::LogStreamsReferenceMapPtr logStreamsReference(void); + /// @brief Default typed configuration based on existing defaultConf + static base::TypedConfigurations defaultTypedConfigurations(void); + /// @brief Populates all logger IDs in current repository. + /// @param [out] targetList List of fill up. + static std::vector* populateAllLoggerIds(std::vector* targetList); + /// @brief Sets configurations from global configuration file. + static void configureFromGlobal(const char* globalConfigurationFilePath); + /// @brief Configures loggers using command line arg. Ensure you have already set command line args, + /// @return False if invalid argument or argument with no value provided, true if attempted to configure logger. + /// If true is returned that does not mean it has been configured successfully, it only means that it + /// has attempted to configure logger using configuration file provided in argument + static bool configureFromArg(const char* argKey); + /// @brief Flushes all loggers for all levels - Be careful if you dont know how many loggers are registered + static void flushAll(void); + /// @brief Adds logging flag used internally. + static inline void addFlag(LoggingFlag flag) { + ELPP->addFlag(flag); + } + /// @brief Removes logging flag used internally. + static inline void removeFlag(LoggingFlag flag) { + ELPP->removeFlag(flag); + } + /// @brief Determines whether or not certain flag is active + static inline bool hasFlag(LoggingFlag flag) { + return ELPP->hasFlag(flag); + } + /// @brief Adds flag and removes it when scope goes out + class ScopedAddFlag { + public: + ScopedAddFlag(LoggingFlag flag) : m_flag(flag) { + Loggers::addFlag(m_flag); + } + ~ScopedAddFlag(void) { + Loggers::removeFlag(m_flag); + } + private: + LoggingFlag m_flag; + }; + /// @brief Removes flag and add it when scope goes out + class ScopedRemoveFlag { + public: + ScopedRemoveFlag(LoggingFlag flag) : m_flag(flag) { + Loggers::removeFlag(m_flag); + } + ~ScopedRemoveFlag(void) { + Loggers::addFlag(m_flag); + } + private: + LoggingFlag m_flag; + }; + /// @brief Sets hierarchy for logging. Needs to enable logging flag (HierarchicalLogging) + static void setLoggingLevel(Level level) { + ELPP->setLoggingLevel(level); + } + /// @brief Sets verbose level on the fly + static void setVerboseLevel(base::type::VerboseLevel level); + /// @brief Gets current verbose level + static base::type::VerboseLevel verboseLevel(void); + /// @brief Sets vmodules as specified (on the fly) + static void setVModules(const char* modules); + /// @brief Clears vmodules + static void clearVModules(void); +}; +class VersionInfo : base::StaticClass { + public: + /// @brief Current version number + static const std::string version(void); + + /// @brief Release date of current version + static const std::string releaseDate(void); +}; +} // namespace el +#undef VLOG_IS_ON +/// @brief Determines whether verbose logging is on for specified level current file. +#define VLOG_IS_ON(verboseLevel) (ELPP->vRegistry()->allowed(verboseLevel, __FILE__)) +#undef TIMED_BLOCK +#undef TIMED_SCOPE +#undef TIMED_SCOPE_IF +#undef TIMED_FUNC +#undef TIMED_FUNC_IF +#undef ELPP_MIN_UNIT +#if defined(ELPP_PERFORMANCE_MICROSECONDS) +# define ELPP_MIN_UNIT el::base::TimestampUnit::Microsecond +#else +# define ELPP_MIN_UNIT el::base::TimestampUnit::Millisecond +#endif // (defined(ELPP_PERFORMANCE_MICROSECONDS)) +/// @brief Performance tracked scope. Performance gets written when goes out of scope using +/// 'performance' logger. +/// +/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); +/// @see el::base::PerformanceTracker +/// @see el::base::PerformanceTracker::checkpoint +// Note: Do not surround this definition with null macro because of obj instance +#define TIMED_SCOPE_IF(obj, blockname, condition) el::base::type::PerformanceTrackerPtr obj( condition ? \ + new el::base::PerformanceTracker(blockname, ELPP_MIN_UNIT) : nullptr ) +#define TIMED_SCOPE(obj, blockname) TIMED_SCOPE_IF(obj, blockname, true) +#define TIMED_BLOCK(obj, blockName) for (struct { int i; el::base::type::PerformanceTrackerPtr timer; } obj = { 0, \ + el::base::type::PerformanceTrackerPtr(new el::base::PerformanceTracker(blockName, ELPP_MIN_UNIT)) }; obj.i < 1; ++obj.i) +/// @brief Performance tracked function. Performance gets written when goes out of scope using +/// 'performance' logger. +/// +/// @detail Please note in order to check the performance at a certain time you can use obj->checkpoint(); +/// @see el::base::PerformanceTracker +/// @see el::base::PerformanceTracker::checkpoint +#define TIMED_FUNC_IF(obj,condition) TIMED_SCOPE_IF(obj, ELPP_FUNC, condition) +#define TIMED_FUNC(obj) TIMED_SCOPE(obj, ELPP_FUNC) +#undef PERFORMANCE_CHECKPOINT +#undef PERFORMANCE_CHECKPOINT_WITH_ID +#define PERFORMANCE_CHECKPOINT(obj) obj->checkpoint(std::string(), __FILE__, __LINE__, ELPP_FUNC) +#define PERFORMANCE_CHECKPOINT_WITH_ID(obj, id) obj->checkpoint(id, __FILE__, __LINE__, ELPP_FUNC) +#undef ELPP_COUNTER +#undef ELPP_COUNTER_POS +/// @brief Gets hit counter for file/line +#define ELPP_COUNTER (ELPP->hitCounters()->getCounter(__FILE__, __LINE__)) +/// @brief Gets hit counter position for file/line, -1 if not registered yet +#define ELPP_COUNTER_POS (ELPP_COUNTER == nullptr ? -1 : ELPP_COUNTER->hitCounts()) +// Undef levels to support LOG(LEVEL) +#undef INFO +#undef WARNING +#undef DEBUG +#undef ERROR +#undef FATAL +#undef TRACE +#undef VERBOSE +// Undef existing +#undef CINFO +#undef CWARNING +#undef CDEBUG +#undef CFATAL +#undef CERROR +#undef CTRACE +#undef CVERBOSE +#undef CINFO_IF +#undef CWARNING_IF +#undef CDEBUG_IF +#undef CERROR_IF +#undef CFATAL_IF +#undef CTRACE_IF +#undef CVERBOSE_IF +#undef CINFO_EVERY_N +#undef CWARNING_EVERY_N +#undef CDEBUG_EVERY_N +#undef CERROR_EVERY_N +#undef CFATAL_EVERY_N +#undef CTRACE_EVERY_N +#undef CVERBOSE_EVERY_N +#undef CINFO_AFTER_N +#undef CWARNING_AFTER_N +#undef CDEBUG_AFTER_N +#undef CERROR_AFTER_N +#undef CFATAL_AFTER_N +#undef CTRACE_AFTER_N +#undef CVERBOSE_AFTER_N +#undef CINFO_N_TIMES +#undef CWARNING_N_TIMES +#undef CDEBUG_N_TIMES +#undef CERROR_N_TIMES +#undef CFATAL_N_TIMES +#undef CTRACE_N_TIMES +#undef CVERBOSE_N_TIMES +// Normal logs +#if ELPP_INFO_LOG +# define CINFO(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE(writer, dispatchAction, ...) ELPP_WRITE_LOG(writer, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE(writer, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE(writer, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel)) writer(\ +el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#else +# define CVERBOSE(writer, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// Conditional logs +#if ELPP_INFO_LOG +# define CINFO_IF(writer, condition_, dispatchAction, ...) \ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_IF(writer, condition_, dispatchAction, ...)\ +ELPP_WRITE_LOG_IF(writer, (condition_), el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_IF(writer, condition_, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) if (VLOG_IS_ON(vlevel) && (condition_)) writer( \ +el::Level::Verbose, __FILE__, __LINE__, ELPP_FUNC, dispatchAction, vlevel).construct(el_getVALength(__VA_ARGS__), __VA_ARGS__) +#else +# define CVERBOSE_IF(writer, condition_, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// Occasional logs +#if ELPP_INFO_LOG +# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...)\ +ELPP_WRITE_LOG_EVERY_N(writer, occasion, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_EVERY_N(writer, occasion, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateEveryNCounter(__FILE__, __LINE__, occasion), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_EVERY_N(writer, occasion, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// After N logs +#if ELPP_INFO_LOG +# define CINFO_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_AFTER_N(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_AFTER_N(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_AFTER_N(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateAfterNCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_AFTER_N(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// N Times logs +#if ELPP_INFO_LOG +# define CINFO_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Info, dispatchAction, __VA_ARGS__) +#else +# define CINFO_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_INFO_LOG +#if ELPP_WARNING_LOG +# define CWARNING_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Warning, dispatchAction, __VA_ARGS__) +#else +# define CWARNING_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_WARNING_LOG +#if ELPP_DEBUG_LOG +# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Debug, dispatchAction, __VA_ARGS__) +#else +# define CDEBUG_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_DEBUG_LOG +#if ELPP_ERROR_LOG +# define CERROR_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Error, dispatchAction, __VA_ARGS__) +#else +# define CERROR_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_ERROR_LOG +#if ELPP_FATAL_LOG +# define CFATAL_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Fatal, dispatchAction, __VA_ARGS__) +#else +# define CFATAL_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_FATAL_LOG +#if ELPP_TRACE_LOG +# define CTRACE_N_TIMES(writer, n, dispatchAction, ...)\ +ELPP_WRITE_LOG_N_TIMES(writer, n, el::Level::Trace, dispatchAction, __VA_ARGS__) +#else +# define CTRACE_N_TIMES(writer, n, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_TRACE_LOG +#if ELPP_VERBOSE_LOG +# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...)\ +CVERBOSE_IF(writer, ELPP->validateNTimesCounter(__FILE__, __LINE__, n), vlevel, dispatchAction, __VA_ARGS__) +#else +# define CVERBOSE_N_TIMES(writer, n, vlevel, dispatchAction, ...) el::base::NullWriter() +#endif // ELPP_VERBOSE_LOG +// +// Custom Loggers - Requires (level, dispatchAction, loggerId/s) +// +// undef existing +#undef CLOG +#undef CLOG_VERBOSE +#undef CVLOG +#undef CLOG_IF +#undef CLOG_VERBOSE_IF +#undef CVLOG_IF +#undef CLOG_EVERY_N +#undef CVLOG_EVERY_N +#undef CLOG_AFTER_N +#undef CVLOG_AFTER_N +#undef CLOG_N_TIMES +#undef CVLOG_N_TIMES +// Normal logs +#define CLOG(LEVEL, ...)\ +C##LEVEL(el::base::Writer, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG(vlevel, ...) CVERBOSE(el::base::Writer, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// Conditional logs +#define CLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_IF(condition, vlevel, ...)\ +CVERBOSE_IF(el::base::Writer, condition, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// Hit counts based logs +#define CLOG_EVERY_N(n, LEVEL, ...)\ +C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_EVERY_N(n, vlevel, ...)\ +CVERBOSE_EVERY_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CLOG_AFTER_N(n, LEVEL, ...)\ +C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_AFTER_N(n, vlevel, ...)\ +CVERBOSE_AFTER_N(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CLOG_N_TIMES(n, LEVEL, ...)\ +C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CVLOG_N_TIMES(n, vlevel, ...)\ +CVERBOSE_N_TIMES(el::base::Writer, n, vlevel, el::base::DispatchAction::NormalLog, __VA_ARGS__) +// +// Default Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros +// +// undef existing +#undef LOG +#undef VLOG +#undef LOG_IF +#undef VLOG_IF +#undef LOG_EVERY_N +#undef VLOG_EVERY_N +#undef LOG_AFTER_N +#undef VLOG_AFTER_N +#undef LOG_N_TIMES +#undef VLOG_N_TIMES +#undef ELPP_CURR_FILE_LOGGER_ID +#if defined(ELPP_DEFAULT_LOGGER) +# define ELPP_CURR_FILE_LOGGER_ID ELPP_DEFAULT_LOGGER +#else +# define ELPP_CURR_FILE_LOGGER_ID el::base::consts::kDefaultLoggerId +#endif +#undef ELPP_TRACE +#define ELPP_TRACE CLOG(TRACE, ELPP_CURR_FILE_LOGGER_ID) +// Normal logs +#define LOG(LEVEL) CLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG(vlevel) CVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Conditional logs +#define LOG_IF(condition, LEVEL) CLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_IF(condition, vlevel) CVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Hit counts based logs +#define LOG_EVERY_N(n, LEVEL) CLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_EVERY_N(n, vlevel) CVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define LOG_AFTER_N(n, LEVEL) CLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_AFTER_N(n, vlevel) CVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define LOG_N_TIMES(n, LEVEL) CLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define VLOG_N_TIMES(n, vlevel) CVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Generic PLOG() +#undef CPLOG +#undef CPLOG_IF +#undef PLOG +#undef PLOG_IF +#undef DCPLOG +#undef DCPLOG_IF +#undef DPLOG +#undef DPLOG_IF +#define CPLOG(LEVEL, ...)\ +C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define CPLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::PErrorWriter, condition, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define DCPLOG(LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL(el::base::PErrorWriter, el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define DCPLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::PErrorWriter, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::NormalLog, __VA_ARGS__) +#define PLOG(LEVEL) CPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define PLOG_IF(condition, LEVEL) CPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DPLOG(LEVEL) DCPLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DPLOG_IF(condition, LEVEL) DCPLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +// Generic SYSLOG() +#undef CSYSLOG +#undef CSYSLOG_IF +#undef CSYSLOG_EVERY_N +#undef CSYSLOG_AFTER_N +#undef CSYSLOG_N_TIMES +#undef SYSLOG +#undef SYSLOG_IF +#undef SYSLOG_EVERY_N +#undef SYSLOG_AFTER_N +#undef SYSLOG_N_TIMES +#undef DCSYSLOG +#undef DCSYSLOG_IF +#undef DCSYSLOG_EVERY_N +#undef DCSYSLOG_AFTER_N +#undef DCSYSLOG_N_TIMES +#undef DSYSLOG +#undef DSYSLOG_IF +#undef DSYSLOG_EVERY_N +#undef DSYSLOG_AFTER_N +#undef DSYSLOG_N_TIMES +#if defined(ELPP_SYSLOG) +# define CSYSLOG(LEVEL, ...)\ +C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, condition, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_EVERY_N(n, LEVEL, ...) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_AFTER_N(n, LEVEL, ...) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define CSYSLOG_N_TIMES(n, LEVEL, ...) C##LEVEL##_N_TIMES(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define SYSLOG(LEVEL) CSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_IF(condition, LEVEL) CSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_EVERY_N(n, LEVEL) CSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_AFTER_N(n, LEVEL) CSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define SYSLOG_N_TIMES(n, LEVEL) CSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DCSYSLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) C##LEVEL(el::base::Writer, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_IF(condition, LEVEL, ...)\ +C##LEVEL##_IF(el::base::Writer, (ELPP_DEBUG_LOG) && (condition), el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_EVERY_N(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_AFTER_N(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_AFTER_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DCSYSLOG_N_TIMES(n, LEVEL, ...)\ +if (ELPP_DEBUG_LOG) C##LEVEL##_EVERY_N(el::base::Writer, n, el::base::DispatchAction::SysLog, __VA_ARGS__) +# define DSYSLOG(LEVEL) DCSYSLOG(LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_IF(condition, LEVEL) DCSYSLOG_IF(condition, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_EVERY_N(n, LEVEL) DCSYSLOG_EVERY_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_AFTER_N(n, LEVEL) DCSYSLOG_AFTER_N(n, LEVEL, el::base::consts::kSysLogLoggerId) +# define DSYSLOG_N_TIMES(n, LEVEL) DCSYSLOG_N_TIMES(n, LEVEL, el::base::consts::kSysLogLoggerId) +#else +# define CSYSLOG(LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() +# define CSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() +# define SYSLOG(LEVEL) el::base::NullWriter() +# define SYSLOG_IF(condition, LEVEL) el::base::NullWriter() +# define SYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() +# define SYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() +# define SYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() +# define DCSYSLOG(LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_IF(condition, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_EVERY_N(n, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_AFTER_N(n, LEVEL, ...) el::base::NullWriter() +# define DCSYSLOG_N_TIMES(n, LEVEL, ...) el::base::NullWriter() +# define DSYSLOG(LEVEL) el::base::NullWriter() +# define DSYSLOG_IF(condition, LEVEL) el::base::NullWriter() +# define DSYSLOG_EVERY_N(n, LEVEL) el::base::NullWriter() +# define DSYSLOG_AFTER_N(n, LEVEL) el::base::NullWriter() +# define DSYSLOG_N_TIMES(n, LEVEL) el::base::NullWriter() +#endif // defined(ELPP_SYSLOG) +// +// Custom Debug Only Loggers - Requires (level, loggerId/s) +// +// undef existing +#undef DCLOG +#undef DCVLOG +#undef DCLOG_IF +#undef DCVLOG_IF +#undef DCLOG_EVERY_N +#undef DCVLOG_EVERY_N +#undef DCLOG_AFTER_N +#undef DCVLOG_AFTER_N +#undef DCLOG_N_TIMES +#undef DCVLOG_N_TIMES +// Normal logs +#define DCLOG(LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG(LEVEL, __VA_ARGS__) +#define DCLOG_VERBOSE(vlevel, ...) if (ELPP_DEBUG_LOG) CLOG_VERBOSE(vlevel, __VA_ARGS__) +#define DCVLOG(vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG(vlevel, __VA_ARGS__) +// Conditional logs +#define DCLOG_IF(condition, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_IF(condition, LEVEL, __VA_ARGS__) +#define DCVLOG_IF(condition, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_IF(condition, vlevel, __VA_ARGS__) +// Hit counts based logs +#define DCLOG_EVERY_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_EVERY_N(n, LEVEL, __VA_ARGS__) +#define DCVLOG_EVERY_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_EVERY_N(n, vlevel, __VA_ARGS__) +#define DCLOG_AFTER_N(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_AFTER_N(n, LEVEL, __VA_ARGS__) +#define DCVLOG_AFTER_N(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_AFTER_N(n, vlevel, __VA_ARGS__) +#define DCLOG_N_TIMES(n, LEVEL, ...) if (ELPP_DEBUG_LOG) CLOG_N_TIMES(n, LEVEL, __VA_ARGS__) +#define DCVLOG_N_TIMES(n, vlevel, ...) if (ELPP_DEBUG_LOG) CVLOG_N_TIMES(n, vlevel, __VA_ARGS__) +// +// Default Debug Only Loggers macro using CLOG(), CLOG_VERBOSE() and CVLOG() macros +// +#if !defined(ELPP_NO_DEBUG_MACROS) +// undef existing +#undef DLOG +#undef DVLOG +#undef DLOG_IF +#undef DVLOG_IF +#undef DLOG_EVERY_N +#undef DVLOG_EVERY_N +#undef DLOG_AFTER_N +#undef DVLOG_AFTER_N +#undef DLOG_N_TIMES +#undef DVLOG_N_TIMES +// Normal logs +#define DLOG(LEVEL) DCLOG(LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG(vlevel) DCVLOG(vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Conditional logs +#define DLOG_IF(condition, LEVEL) DCLOG_IF(condition, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_IF(condition, vlevel) DCVLOG_IF(condition, vlevel, ELPP_CURR_FILE_LOGGER_ID) +// Hit counts based logs +#define DLOG_EVERY_N(n, LEVEL) DCLOG_EVERY_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_EVERY_N(n, vlevel) DCVLOG_EVERY_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define DLOG_AFTER_N(n, LEVEL) DCLOG_AFTER_N(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_AFTER_N(n, vlevel) DCVLOG_AFTER_N(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#define DLOG_N_TIMES(n, LEVEL) DCLOG_N_TIMES(n, LEVEL, ELPP_CURR_FILE_LOGGER_ID) +#define DVLOG_N_TIMES(n, vlevel) DCVLOG_N_TIMES(n, vlevel, ELPP_CURR_FILE_LOGGER_ID) +#endif // defined(ELPP_NO_DEBUG_MACROS) +#if !defined(ELPP_NO_CHECK_MACROS) +// Check macros +#undef CCHECK +#undef CPCHECK +#undef CCHECK_EQ +#undef CCHECK_NE +#undef CCHECK_LT +#undef CCHECK_GT +#undef CCHECK_LE +#undef CCHECK_GE +#undef CCHECK_BOUNDS +#undef CCHECK_NOTNULL +#undef CCHECK_STRCASEEQ +#undef CCHECK_STRCASENE +#undef CHECK +#undef PCHECK +#undef CHECK_EQ +#undef CHECK_NE +#undef CHECK_LT +#undef CHECK_GT +#undef CHECK_LE +#undef CHECK_GE +#undef CHECK_BOUNDS +#undef CHECK_NOTNULL +#undef CHECK_STRCASEEQ +#undef CHECK_STRCASENE +#define CCHECK(condition, ...) CLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " +#define CPCHECK(condition, ...) CPLOG_IF(!(condition), FATAL, __VA_ARGS__) << "Check failed: [" << #condition << "] " +#define CHECK(condition) CCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define PCHECK(condition) CPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define CCHECK_EQ(a, b, ...) CCHECK(a == b, __VA_ARGS__) +#define CCHECK_NE(a, b, ...) CCHECK(a != b, __VA_ARGS__) +#define CCHECK_LT(a, b, ...) CCHECK(a < b, __VA_ARGS__) +#define CCHECK_GT(a, b, ...) CCHECK(a > b, __VA_ARGS__) +#define CCHECK_LE(a, b, ...) CCHECK(a <= b, __VA_ARGS__) +#define CCHECK_GE(a, b, ...) CCHECK(a >= b, __VA_ARGS__) +#define CCHECK_BOUNDS(val, min, max, ...) CCHECK(val >= min && val <= max, __VA_ARGS__) +#define CHECK_EQ(a, b) CCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_NE(a, b) CCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_LT(a, b) CCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_GT(a, b) CCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_LE(a, b) CCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_GE(a, b) CCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_BOUNDS(val, min, max) CCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) +#define CCHECK_NOTNULL(ptr, ...) CCHECK((ptr) != nullptr, __VA_ARGS__) +#define CCHECK_STREQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " == " << #str2 << "] " +#define CCHECK_STRNE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " != " << #str2 << "] " +#define CCHECK_STRCASEEQ(str1, str2, ...) CLOG_IF(!el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " == " << #str2 << "] " +#define CCHECK_STRCASENE(str1, str2, ...) CLOG_IF(el::base::utils::Str::cStringCaseEq(str1, str2), FATAL, __VA_ARGS__) \ +<< "Check failed: [" << #str1 << " != " << #str2 << "] " +#define CHECK_NOTNULL(ptr) CCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STREQ(str1, str2) CCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRNE(str1, str2) CCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRCASEEQ(str1, str2) CCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define CHECK_STRCASENE(str1, str2) CCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#undef DCCHECK +#undef DCCHECK_EQ +#undef DCCHECK_NE +#undef DCCHECK_LT +#undef DCCHECK_GT +#undef DCCHECK_LE +#undef DCCHECK_GE +#undef DCCHECK_BOUNDS +#undef DCCHECK_NOTNULL +#undef DCCHECK_STRCASEEQ +#undef DCCHECK_STRCASENE +#undef DCPCHECK +#undef DCHECK +#undef DCHECK_EQ +#undef DCHECK_NE +#undef DCHECK_LT +#undef DCHECK_GT +#undef DCHECK_LE +#undef DCHECK_GE +#undef DCHECK_BOUNDS_ +#undef DCHECK_NOTNULL +#undef DCHECK_STRCASEEQ +#undef DCHECK_STRCASENE +#undef DPCHECK +#define DCCHECK(condition, ...) if (ELPP_DEBUG_LOG) CCHECK(condition, __VA_ARGS__) +#define DCCHECK_EQ(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_EQ(a, b, __VA_ARGS__) +#define DCCHECK_NE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_NE(a, b, __VA_ARGS__) +#define DCCHECK_LT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LT(a, b, __VA_ARGS__) +#define DCCHECK_GT(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GT(a, b, __VA_ARGS__) +#define DCCHECK_LE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_LE(a, b, __VA_ARGS__) +#define DCCHECK_GE(a, b, ...) if (ELPP_DEBUG_LOG) CCHECK_GE(a, b, __VA_ARGS__) +#define DCCHECK_BOUNDS(val, min, max, ...) if (ELPP_DEBUG_LOG) CCHECK_BOUNDS(val, min, max, __VA_ARGS__) +#define DCCHECK_NOTNULL(ptr, ...) if (ELPP_DEBUG_LOG) CCHECK_NOTNULL((ptr), __VA_ARGS__) +#define DCCHECK_STREQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STREQ(str1, str2, __VA_ARGS__) +#define DCCHECK_STRNE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRNE(str1, str2, __VA_ARGS__) +#define DCCHECK_STRCASEEQ(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASEEQ(str1, str2, __VA_ARGS__) +#define DCCHECK_STRCASENE(str1, str2, ...) if (ELPP_DEBUG_LOG) CCHECK_STRCASENE(str1, str2, __VA_ARGS__) +#define DCPCHECK(condition, ...) if (ELPP_DEBUG_LOG) CPCHECK(condition, __VA_ARGS__) +#define DCHECK(condition) DCCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_EQ(a, b) DCCHECK_EQ(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_NE(a, b) DCCHECK_NE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_LT(a, b) DCCHECK_LT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_GT(a, b) DCCHECK_GT(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_LE(a, b) DCCHECK_LE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_GE(a, b) DCCHECK_GE(a, b, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_BOUNDS(val, min, max) DCCHECK_BOUNDS(val, min, max, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_NOTNULL(ptr) DCCHECK_NOTNULL((ptr), ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STREQ(str1, str2) DCCHECK_STREQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRNE(str1, str2) DCCHECK_STRNE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRCASEEQ(str1, str2) DCCHECK_STRCASEEQ(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DCHECK_STRCASENE(str1, str2) DCCHECK_STRCASENE(str1, str2, ELPP_CURR_FILE_LOGGER_ID) +#define DPCHECK(condition) DCPCHECK(condition, ELPP_CURR_FILE_LOGGER_ID) +#endif // defined(ELPP_NO_CHECK_MACROS) +#if defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) +# define ELPP_USE_DEF_CRASH_HANDLER false +#else +# define ELPP_USE_DEF_CRASH_HANDLER true +#endif // defined(ELPP_DISABLE_DEFAULT_CRASH_HANDLING) +#define ELPP_CRASH_HANDLER_INIT +#define ELPP_INIT_EASYLOGGINGPP(val) \ +namespace el { \ +namespace base { \ +el::base::type::StoragePointer elStorage(val); \ +} \ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER); \ +} + +#if ELPP_ASYNC_LOGGING +# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()),\ +new el::base::AsyncDispatchWorker())) +#else +# define INITIALIZE_EASYLOGGINGPP ELPP_INIT_EASYLOGGINGPP(new el::base::Storage(el::LogBuilderPtr(new el::base::DefaultLogBuilder()))) +#endif // ELPP_ASYNC_LOGGING +#define INITIALIZE_NULL_EASYLOGGINGPP \ +namespace el {\ +namespace base {\ +el::base::type::StoragePointer elStorage;\ +}\ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ +} +#define SHARE_EASYLOGGINGPP(initializedStorage)\ +namespace el {\ +namespace base {\ +el::base::type::StoragePointer elStorage(initializedStorage);\ +}\ +el::base::debug::CrashHandler elCrashHandler(ELPP_USE_DEF_CRASH_HANDLER);\ +} + +#if defined(ELPP_UNICODE) +# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv); std::locale::global(std::locale("")) +#else +# define START_EASYLOGGINGPP(argc, argv) el::Helpers::setArgs(argc, argv) +#endif // defined(ELPP_UNICODE) +#endif // EASYLOGGINGPP_H diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000..4a0d65d Binary files /dev/null and b/logo.ico differ diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..0c85b13 Binary files /dev/null and b/logo.png differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..5ba1f9f --- /dev/null +++ b/main.cpp @@ -0,0 +1,33 @@ +#include "homewindow.h" +#include +#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(); +} diff --git a/medialist.cpp b/medialist.cpp new file mode 100644 index 0000000..600a1ee --- /dev/null +++ b/medialist.cpp @@ -0,0 +1,77 @@ +#include +#include +#include "medialist.h" +#include "urldialog.h" +#include "easylogging++.h" +//#include +//#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()); +} diff --git a/medialist.h b/medialist.h new file mode 100644 index 0000000..298955b --- /dev/null +++ b/medialist.h @@ -0,0 +1,35 @@ +#ifndef MEDIALIST_H +#define MEDIALIST_H + + +#include +#include +#include +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 diff --git a/player.ico b/player.ico new file mode 100644 index 0000000..9182c0e Binary files /dev/null and b/player.ico differ diff --git a/playlist.cpp b/playlist.cpp new file mode 100644 index 0000000..aad4e91 --- /dev/null +++ b/playlist.cpp @@ -0,0 +1,275 @@ +#include + +#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 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 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 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 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(); // 获取选中后的新位置。 +} diff --git a/playlist.h b/playlist.h new file mode 100644 index 0000000..d992d93 --- /dev/null +++ b/playlist.h @@ -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 +#include +#include +#include +#include + +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 diff --git a/playlist.ui b/playlist.ui new file mode 100644 index 0000000..2347158 --- /dev/null +++ b/playlist.ui @@ -0,0 +1,71 @@ + + + Playlist + + + + 0 + 0 + 115 + 254 + + + + Form + + + + 0 + + + 1 + + + 0 + + + 0 + + + 0 + + + + + Qt::NoFocus + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAlwaysOff + + + + testlist0 + + + + + testlist1 + + + + + testlist2 + + + + + + + + + MediaList + QListWidget +
medialist.h
+
+
+ + +
diff --git a/playlistwind.cpp b/playlistwind.cpp new file mode 100644 index 0000000..148d583 --- /dev/null +++ b/playlistwind.cpp @@ -0,0 +1,254 @@ +#include +#include +#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 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 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 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 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(); // 获取选中后的新位置。 +} diff --git a/playlistwind.h b/playlistwind.h new file mode 100644 index 0000000..2c87045 --- /dev/null +++ b/playlistwind.h @@ -0,0 +1,83 @@ +#ifndef PLAYLISTWIND_H +#define PLAYLISTWIND_H + +#include +#include +#include +#include +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 diff --git a/playlistwind.ui b/playlistwind.ui new file mode 100644 index 0000000..9fa1da0 --- /dev/null +++ b/playlistwind.ui @@ -0,0 +1,66 @@ + + + PlayListWind + + + + 0 + 0 + 150 + 254 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 周杰伦-双截棍 + + + + + 255 + 0 + 0 + + + + + + + 林俊杰 + + + + + + + + + MediaList + QListWidget +
medialist.h
+
+
+ + +
diff --git a/res/array_down.png b/res/array_down.png new file mode 100644 index 0000000..2a17fad Binary files /dev/null and b/res/array_down.png differ diff --git a/res/checkbox_checked.png b/res/checkbox_checked.png new file mode 100644 index 0000000..835c6be Binary files /dev/null and b/res/checkbox_checked.png differ diff --git a/res/checkbox_checked_disable.png b/res/checkbox_checked_disable.png new file mode 100644 index 0000000..21a3b02 Binary files /dev/null and b/res/checkbox_checked_disable.png differ diff --git a/res/checkbox_parcial.png b/res/checkbox_parcial.png new file mode 100644 index 0000000..5032713 Binary files /dev/null and b/res/checkbox_parcial.png differ diff --git a/res/checkbox_parcial_disable.png b/res/checkbox_parcial_disable.png new file mode 100644 index 0000000..b2b9939 Binary files /dev/null and b/res/checkbox_parcial_disable.png differ diff --git a/res/checkbox_unchecked.png b/res/checkbox_unchecked.png new file mode 100644 index 0000000..993fc92 Binary files /dev/null and b/res/checkbox_unchecked.png differ diff --git a/res/checkbox_unchecked_disable.png b/res/checkbox_unchecked_disable.png new file mode 100644 index 0000000..8095647 Binary files /dev/null and b/res/checkbox_unchecked_disable.png differ diff --git a/res/fa-solid-900.ttf b/res/fa-solid-900.ttf new file mode 100644 index 0000000..a1da1bb Binary files /dev/null and b/res/fa-solid-900.ttf differ diff --git a/res/fontawesome-webfont.ttf b/res/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/res/fontawesome-webfont.ttf differ diff --git a/res/icon/fast-backward.png b/res/icon/fast-backward.png new file mode 100644 index 0000000..7c56ccf Binary files /dev/null and b/res/icon/fast-backward.png differ diff --git a/res/icon/fast-forward.png b/res/icon/fast-forward.png new file mode 100644 index 0000000..d58221f Binary files /dev/null and b/res/icon/fast-forward.png differ diff --git a/res/icon/next.png b/res/icon/next.png new file mode 100644 index 0000000..5f69233 Binary files /dev/null and b/res/icon/next.png differ diff --git a/res/icon/pause.png b/res/icon/pause.png new file mode 100644 index 0000000..399cc49 Binary files /dev/null and b/res/icon/pause.png differ diff --git a/res/icon/play.png b/res/icon/play.png new file mode 100644 index 0000000..00391ae Binary files /dev/null and b/res/icon/play.png differ diff --git a/res/icon/previous.png b/res/icon/previous.png new file mode 100644 index 0000000..f61599b Binary files /dev/null and b/res/icon/previous.png differ diff --git a/res/icon/quick.png b/res/icon/quick.png new file mode 100644 index 0000000..c8f0f53 Binary files /dev/null and b/res/icon/quick.png differ diff --git a/res/icon/resize.png b/res/icon/resize.png new file mode 100644 index 0000000..dab3981 Binary files /dev/null and b/res/icon/resize.png differ diff --git a/res/icon/rules.png b/res/icon/rules.png new file mode 100644 index 0000000..0e489b8 Binary files /dev/null and b/res/icon/rules.png differ diff --git a/res/icon/screenshot.png b/res/icon/screenshot.png new file mode 100644 index 0000000..083dec0 Binary files /dev/null and b/res/icon/screenshot.png differ diff --git a/res/icon/stop-button.png b/res/icon/stop-button.png new file mode 100644 index 0000000..5b03ad0 Binary files /dev/null and b/res/icon/stop-button.png differ diff --git a/res/icon/tune.png b/res/icon/tune.png new file mode 100644 index 0000000..0c3b456 Binary files /dev/null and b/res/icon/tune.png differ diff --git a/res/qss/about.css b/res/qss/about.css new file mode 100644 index 0000000..689fb54 --- /dev/null +++ b/res/qss/about.css @@ -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); +} diff --git a/res/qss/ctrlbar.css b/res/qss/ctrlbar.css new file mode 100644 index 0000000..4d9b6c0 --- /dev/null +++ b/res/qss/ctrlbar.css @@ -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; +} diff --git a/res/qss/homewindow.css b/res/qss/homewindow.css new file mode 100644 index 0000000..110cac9 --- /dev/null +++ b/res/qss/homewindow.css @@ -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; +}*/ diff --git a/res/qss/homewindow.qss b/res/qss/homewindow.qss new file mode 100644 index 0000000..c6db82d --- /dev/null +++ b/res/qss/homewindow.qss @@ -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; +}*/ diff --git a/res/qss/playlist.css b/res/qss/playlist.css new file mode 100644 index 0000000..13f2898 --- /dev/null +++ b/res/qss/playlist.css @@ -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; +} diff --git a/res/qss/show.css b/res/qss/show.css new file mode 100644 index 0000000..bfd3697 --- /dev/null +++ b/res/qss/show.css @@ -0,0 +1,6 @@ +QWidget{ + background-color: #1a1a20; + color:white; + border: 1px solid black; + border-bottom: none; +} diff --git a/res/qss/title.css b/res/qss/title.css new file mode 100644 index 0000000..867737d --- /dev/null +++ b/res/qss/title.css @@ -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; +} diff --git a/res/radio_normal.png b/res/radio_normal.png new file mode 100644 index 0000000..20ceeee Binary files /dev/null and b/res/radio_normal.png differ diff --git a/res/radio_selected.png b/res/radio_selected.png new file mode 100644 index 0000000..73feef7 Binary files /dev/null and b/res/radio_selected.png differ diff --git a/res/radiobutton_checked.png b/res/radiobutton_checked.png new file mode 100644 index 0000000..23f7537 Binary files /dev/null and b/res/radiobutton_checked.png differ diff --git a/res/radiobutton_checked_disable.png b/res/radiobutton_checked_disable.png new file mode 100644 index 0000000..dde88b9 Binary files /dev/null and b/res/radiobutton_checked_disable.png differ diff --git a/res/radiobutton_unchecked.png b/res/radiobutton_unchecked.png new file mode 100644 index 0000000..d1c2191 Binary files /dev/null and b/res/radiobutton_unchecked.png differ diff --git a/res/radiobutton_unchecked_disable.png b/res/radiobutton_unchecked_disable.png new file mode 100644 index 0000000..40413e1 Binary files /dev/null and b/res/radiobutton_unchecked_disable.png differ diff --git a/resource.qrc b/resource.qrc new file mode 100644 index 0000000..5588cc1 --- /dev/null +++ b/resource.qrc @@ -0,0 +1,34 @@ + + + logo.ico + + + res/qss/homewindow.qss + res/array_down.png + res/checkbox_checked.png + res/checkbox_checked_disable.png + res/checkbox_parcial.png + res/checkbox_parcial_disable.png + res/checkbox_unchecked.png + res/checkbox_unchecked_disable.png + res/radio_normal.png + res/radio_selected.png + res/radiobutton_checked.png + res/radiobutton_checked_disable.png + res/radiobutton_unchecked.png + + + res/icon/fast-backward.png + res/icon/fast-forward.png + res/icon/next.png + res/icon/pause.png + res/icon/play.png + res/icon/previous.png + res/icon/resize.png + res/icon/rules.png + res/icon/screenshot.png + res/icon/stop-button.png + res/icon/tune.png + res/icon/quick.png + + diff --git a/screenshot.cpp b/screenshot.cpp new file mode 100644 index 0000000..739d379 --- /dev/null +++ b/screenshot.cpp @@ -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; +} + diff --git a/screenshot.h b/screenshot.h new file mode 100644 index 0000000..79851aa --- /dev/null +++ b/screenshot.h @@ -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 +#include +#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 diff --git a/setting.cpp b/setting.cpp new file mode 100644 index 0000000..b154a29 --- /dev/null +++ b/setting.cpp @@ -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; +} diff --git a/setting.h b/setting.h new file mode 100644 index 0000000..d0604bd --- /dev/null +++ b/setting.h @@ -0,0 +1,22 @@ +#ifndef SETTING_H +#define SETTING_H + +#include + +namespace Ui { +class Setting; +} + +class Setting : public QWidget +{ + Q_OBJECT + +public: + explicit Setting(QWidget *parent = nullptr); + ~Setting(); + +private: + Ui::Setting *ui; +}; + +#endif // SETTING_H diff --git a/setting.ui b/setting.ui new file mode 100644 index 0000000..a1e1ba6 --- /dev/null +++ b/setting.ui @@ -0,0 +1,269 @@ + + + Setting + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + 80 + 90 + 274 + 79 + + + + + + + + 60 + 0 + + + + + 70 + 30 + + + + 音频缓存 + + + + + + + + 0 + 0 + + + + + 50 + 0 + + + + + 60 + 16777215 + + + + 视频缓存 + + + + + + + + 0 + 0 + + + + + 80 + 30 + + + + + 30ms + + + + + 100ms + + + + + 200ms + + + + + 400ms + + + + + 600ms + + + + + 800ms + + + + + 1000ms + + + + + + + + + 60 + 0 + + + + + 70 + 30 + + + + 视频缓存 + + + + + + + + 0 + 0 + + + + + 80 + 30 + + + + + 30ms + + + + + 100ms + + + + + 200ms + + + + + 400ms + + + + + 600ms + + + + + 800ms + + + + + 1000ms + + + + + 2000ms + + + + + 4000ms + + + + + + + + + 50 + 0 + + + + + 60 + 16777215 + + + + 音频缓存 + + + + + + + + 40 + 0 + + + + + 60 + 16777215 + + + + 缓存阈值 + + + + + + + + 0 + 0 + + + + + 40 + 0 + + + + + 60 + 16777215 + + + + 抖动值 + + + + + + + + + diff --git a/sonic.cpp b/sonic.cpp new file mode 100644 index 0000000..5f02208 --- /dev/null +++ b/sonic.cpp @@ -0,0 +1,1356 @@ +/* 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. +*/ + +#include +#include +#include +#include +#include +#include +#include "sonic.h" +//#include "webrtc/base/logging.h" +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* + The following code was used to generate the following sinc lookup table. + + #include + #include + #include + + double findHannWeight(int N, double x) { + return 0.5*(1.0 - cos(2*M_PI*x/N)); + } + + double findSincCoefficient(int N, double x) { + double hannWindowWeight = findHannWeight(N, x); + double sincWeight; + + x -= N/2.0; + if (x > 1e-9 || x < -1e-9) { + sincWeight = sin(M_PI*x)/(M_PI*x); + } else { + sincWeight = 1.0; + } + return hannWindowWeight*sincWeight; + } + + int main() { + double x; + int i; + int N = 12; + + for (i = 0, x = 0.0; x <= N; x += 0.02, i++) { + printf("%u %d\n", i, (int)(SHRT_MAX*findSincCoefficient(N, x))); + } + return 0; + } +*/ + +/* The number of points to use in the sinc FIR filter for resampling. */ +#define SINC_FILTER_POINTS 12 /* I am not able to hear improvement with higher N. */ +#define SINC_TABLE_SIZE 601 + +/* Lookup table for windowed sinc function of SINC_FILTER_POINTS points. */ +static short sincTable[SINC_TABLE_SIZE] = { + 0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2, -3, -4, -6, -7, -9, -10, -12, -14, + -17, -19, -21, -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48, -50, + -51, -52, -53, -53, -53, -52, -50, -48, -46, -43, -39, -34, -29, -22, -16, + -8, 0, 9, 19, 29, 41, 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200, + 215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348, 357, 363, 369, 372, + 374, 375, 373, 369, 363, 355, 345, 332, 318, 300, 281, 259, 234, 208, 178, + 147, 113, 77, 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426, + -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951, -989, + -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151, + -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728, + -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342, 462, 584, 708, + 833, 958, 1084, 1209, 1333, 1455, 1575, 1693, 1807, 1916, 2022, 2122, 2216, + 2304, 2384, 2457, 2522, 2579, 2625, 2663, 2689, 2706, 2711, 2705, 2687, + 2657, 2614, 2559, 2491, 2411, 2317, 2211, 2092, 1960, 1815, 1658, 1489, + 1308, 1115, 912, 698, 474, 241, 0, -249, -506, -769, -1037, -1310, -1586, + -1864, -2144, -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291, + -4529, -4757, -4972, -5174, -5360, -5531, -5685, -5819, -5935, -6029, + -6101, -6150, -6175, -6175, -6149, -6096, -6015, -5905, -5767, -5599, + -5401, -5172, -4912, -4621, -4298, -3944, -3558, -3141, -2693, -2214, + -1705, -1166, -597, 0, 625, 1277, 1955, 2658, 3386, 4135, 4906, 5697, 6506, + 7332, 8173, 9027, 9893, 10769, 11654, 12544, 13439, 14335, 15232, 16128, + 17019, 17904, 18782, 19649, 20504, 21345, 22170, 22977, 23763, 24527, + 25268, 25982, 26669, 27327, 27953, 28547, 29107, 29632, 30119, 30569, + 30979, 31349, 31678, 31964, 32208, 32408, 32565, 32677, 32744, 32767, + 32744, 32677, 32565, 32408, 32208, 31964, 31678, 31349, 30979, 30569, + 30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982, 25268, 24527, + 23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019, 16128, + 15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332, 6506, + 5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597, -1166, -1705, + -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172, -5401, + -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101, + -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529, + -4291, -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864, + -1586, -1310, -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308, + 1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614, 2657, + 2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457, 2384, 2304, + 2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333, 1209, 1084, 958, 833, + 708, 584, 462, 342, 225, 111, 0, -107, -210, -309, -403, -492, -576, -655, + -728, -796, -857, -913, -963, -1007, -1046, -1078, -1105, -1125, -1141, + -1151, -1155, -1154, -1149, -1138, -1123, -1104, -1080, -1053, -1023, -989, + -951, -912, -870, -825, -779, -731, -682, -632, -581, -530, -478, -426, + -375, -324, -274, -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178, + 208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369, 373, 375, 374, 372, + 369, 363, 357, 348, 339, 328, 317, 304, 291, 276, 262, 247, 231, 215, 200, + 184, 168, 152, 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9, 0, -8, -16, + -22, -29, -34, -39, -43, -46, -48, -50, -52, -53, -53, -53, -52, -51, -50, + -48, -47, -44, -42, -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14, + -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0 +}; + +struct sonicStreamStruct { + short *inputBuffer; + short *outputBuffer; + short *pitchBuffer; + short *downSampleBuffer; + float speed; + float volume; + float pitch; + float rate; + int oldRatePosition; + int newRatePosition; + int useChordPitch; + int quality; + int numChannels; + int inputBufferSize; + int pitchBufferSize; + int outputBufferSize; + int numInputSamples; + int numOutputSamples; + int numPitchSamples; + int minPeriod; + int maxPeriod; + int maxRequired; + int remainingInputToCopy; + int sampleRate; + int prevPeriod; + int prevMinDiff; + float avePower; +}; + +/* Scale the samples by the factor. */ +// 改变音量 +static void scaleSamples( + short *samples, + int numSamples, + float volume) +{ + int fixedPointVolume = volume*4096.0f; + int value; + + while(numSamples--) { + value = (*samples*fixedPointVolume) >> 12; + if(value > 32767) { + value = 32767; + } else if(value < -32767) { + value = -32767; + } + *samples++ = value; + } +} + +/* Get the speed of the stream. */ +// 得到流的速度 +float sonicGetSpeed( + sonicStream stream) +{ + return stream->speed; +} + +/* Set the speed of the stream. */ +// 设置流的速度 +void sonicSetSpeed( + sonicStream stream, + float speed) +{ + stream->speed = speed; +} + +/* Get the pitch of the stream. */ +// 得到流的音调 +float sonicGetPitch( + sonicStream stream) +{ + return stream->pitch; +} + +/* Set the pitch of the stream. */ +// 设置流的音调 +void sonicSetPitch( + sonicStream stream, + float pitch) +{ + stream->pitch = pitch; +} + +/* Get the rate of the stream. */ +// 得到流的速率 +float sonicGetRate( + sonicStream stream) +{ + return stream->rate; +} + +/* Set the playback rate of the stream. This scales pitch and speed at the same time. */ +// 设置回放流的速率,同时也重设pitch和speed +void sonicSetRate( + sonicStream stream, + float rate) +{ + stream->rate = rate; + + stream->oldRatePosition = 0; + stream->newRatePosition = 0; +} + +/* Get the vocal chord pitch setting. */ +// +int sonicGetChordPitch( + sonicStream stream) +{ + return stream->useChordPitch; +} + +/* Set the vocal chord mode for pitch computation. Default is off. */ +void sonicSetChordPitch( + sonicStream stream, + int useChordPitch) +{ + stream->useChordPitch = useChordPitch; +} + +/* Get the quality setting. */ +int sonicGetQuality( + sonicStream stream) +{ + return stream->quality; +} + +/* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */ +void sonicSetQuality( + sonicStream stream, + int quality) +{ + stream->quality = quality; +} + +/* Get the scaling factor of the stream. */ +float sonicGetVolume( + sonicStream stream) +{ + return stream->volume; +} + +/* Set the scaling factor of the stream. */ +// 设置流的音量 +void sonicSetVolume( + sonicStream stream, + float volume) +{ + stream->volume = volume; +} + +/* Free stream buffers. */ +// 释放流内的缓冲区 +static void freeStreamBuffers( + sonicStream stream) +{ + if(stream->inputBuffer != NULL) { + free(stream->inputBuffer); + } + if(stream->outputBuffer != NULL) { + free(stream->outputBuffer); + } + if(stream->pitchBuffer != NULL) { + free(stream->pitchBuffer); + } + if(stream->downSampleBuffer != NULL) { + free(stream->downSampleBuffer); + } +} + +/* Destroy the sonic stream. */ +// 销毁流 +void sonicDestroyStream( + sonicStream stream) +{ + freeStreamBuffers(stream); + free(stream); +} + +/* Allocate stream buffers. */ +/** + * 开辟流的数据缓存空间 + * stream 流 + * sampleRate 采样率 + * numChnnels 声道数 + */ +static int allocateStreamBuffers( + sonicStream stream, + int sampleRate, + int numChannels) +{ // 最小的pitch周期 44100/400 = 110 + int minPeriod = sampleRate/SONIC_MAX_PITCH; + // 最大的pitch周期 44100/65 = 678 个采样点 + int maxPeriod = sampleRate/SONIC_MIN_PITCH; + // 最大 1356 + int maxRequired = 2*maxPeriod; + // 输入缓冲区的大小 = maxRequired + stream->inputBufferSize = maxRequired; + // 为inputBuffer开辟空间并初始化为0 + stream->inputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); + // 如果开辟失败返回0 + if(stream->inputBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + // 输出缓冲区的大小= maxRequired + stream->outputBufferSize = maxRequired; + // 为oututBUffer开辟空间 + stream->outputBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); + if(stream->outputBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + // 为pitchBuffer开辟空间 + stream->pitchBufferSize = maxRequired; + stream->pitchBuffer = (short *)calloc(maxRequired, sizeof(short)*numChannels); + if(stream->pitchBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + // 为downSampleBuffer(降采样)开辟空间 + stream->downSampleBuffer = (short *)calloc(maxRequired, sizeof(short)); + if(stream->downSampleBuffer == NULL) { + sonicDestroyStream(stream); + return 0; + } + // 初始化各项参数 + stream->sampleRate = sampleRate; + stream->numChannels = numChannels; + stream->oldRatePosition = 0; + stream->newRatePosition = 0; + stream->minPeriod = minPeriod; + stream->maxPeriod = maxPeriod; + stream->maxRequired = maxRequired; + stream->prevPeriod = 0; + return 1; +} + +/* Create a sonic stream. Return NULL only if we are out of memory and cannot + allocate the stream. */ +// 创建一个音频流 +sonicStream sonicCreateStream( + int sampleRate, + int numChannels) +{ + // 开辟一个sonicStreamStruct大小的空间 + sonicStream stream = (sonicStream)calloc(1, sizeof(struct sonicStreamStruct)); + // 如果流为空,证明开辟失败 + if(stream == NULL) { + return NULL; + } + if(!allocateStreamBuffers(stream, sampleRate, numChannels)) { + return NULL; + } + // 初始化各项参数 + stream->speed = 1.0f; + stream->pitch = 1.0f; + stream->volume = 1.0f; + stream->rate = 1.0f; + stream->oldRatePosition = 0; + stream->newRatePosition = 0; + stream->useChordPitch = 0; + stream->quality = 0; + stream->avePower = 50.0f; + return stream; +} + +/* Get the sample rate of the stream. */ +// 取得流的采样率 +int sonicGetSampleRate( + sonicStream stream) +{ + return stream->sampleRate; +} + +/* Set the sample rate of the stream. This will cause samples buffered in the stream to + be lost. */ +// 设置流的采样率,可能使流中的已经缓冲的数据丢失 +void sonicSetSampleRate( + sonicStream stream, + int sampleRate) +{ + freeStreamBuffers(stream); + allocateStreamBuffers(stream, sampleRate, stream->numChannels); +} + +/* Get the number of channels. */ +// 取得流的声道的数量 +int sonicGetNumChannels( + sonicStream stream) +{ + return stream->numChannels; +} + +/* Set the num channels of the stream. This will cause samples buffered in the stream to + be lost. */ +// 设置流的声道数量,可能造成流中已缓存的额数据的丢失 +void sonicSetNumChannels( + sonicStream stream, + int numChannels) +{ + freeStreamBuffers(stream); + allocateStreamBuffers(stream, stream->sampleRate, numChannels); +} + +/* Enlarge the output buffer if needed. */ +// 根据需要扩大输出缓冲区 +static int enlargeOutputBufferIfNeeded( + sonicStream stream, + int numSamples) +{ + if(stream->numOutputSamples + numSamples > stream->outputBufferSize) { + stream->outputBufferSize += (stream->outputBufferSize >> 1) + numSamples; + stream->outputBuffer = (short *)realloc(stream->outputBuffer, + stream->outputBufferSize*sizeof(short)*stream->numChannels); + if(stream->outputBuffer == NULL) { + return 0; + } + } + return 1; +} + +/* Enlarge the input buffer if needed. */ +// 如果需要的话增大输入缓冲区 +static int enlargeInputBufferIfNeeded( + sonicStream stream, + int numSamples) +{ + // 流中已经有的采样数据的大小 + 新的采样点个数 + if(stream->numInputSamples + numSamples > stream->inputBufferSize) { + stream->inputBufferSize += (stream->inputBufferSize >> 1) + numSamples; + // 重新设置内存空间的大小 + stream->inputBuffer = (short *)realloc(stream->inputBuffer, + stream->inputBufferSize*sizeof(short)*stream->numChannels); + if(stream->inputBuffer == NULL) { + return 0; + } + } + return 1; +} + +/* Add the input samples to the input buffer. */ +// 向流的输入缓冲区中写入float格式的采样数据 +static int addFloatSamplesToInputBuffer( + sonicStream stream, + float *samples, + int numSamples) +{ + short *buffer; + int count = numSamples*stream->numChannels; + + if(numSamples == 0) { + return 1; + } + if(!enlargeInputBufferIfNeeded(stream, numSamples)) { + return 0; + } + buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels; + while(count--) { + *buffer++ = (*samples++)*32767.0f; + } + stream->numInputSamples += numSamples; + return 1; +} + +/* Add the input samples to the input buffer. */ +// 向流的输入缓冲区中写入short类型的数据 +static int addShortSamplesToInputBuffer( + sonicStream stream, + short *samples, + int numSamples) +{ + if(numSamples == 0) { + return 1; + } + if(!enlargeInputBufferIfNeeded(stream, numSamples)) { + return 0; + } + // 向输入缓冲区拷贝数据,重设numInputSamples大小 + memcpy(stream->inputBuffer + stream->numInputSamples*stream->numChannels, samples, + numSamples*sizeof(short)*stream->numChannels); + stream->numInputSamples += numSamples; + return 1; +} + +/* Add the input samples to the input buffer. */ +// 向流的输如缓冲区中写入unsigned格式的采样数据 +static int addUnsignedCharSamplesToInputBuffer( + sonicStream stream, + unsigned char *samples, + int numSamples) +{ + short *buffer; + int count = numSamples*stream->numChannels; + + if(numSamples == 0) { + return 1; + } + if(!enlargeInputBufferIfNeeded(stream, numSamples)) { + return 0; + } + buffer = stream->inputBuffer + stream->numInputSamples*stream->numChannels; + while(count--) { + *buffer++ = (*samples++ - 128) << 8; + } + stream->numInputSamples += numSamples; + return 1; +} + +/* Remove input samples that we have already processed. */ +// 移除已经处理过的输入缓冲区中的数据 +static void removeInputSamples( + sonicStream stream, + int position) +{ + int remainingSamples = stream->numInputSamples - position; + + if(remainingSamples > 0) { + memmove(stream->inputBuffer, stream->inputBuffer + position*stream->numChannels, + remainingSamples*sizeof(short)*stream->numChannels); + } + stream->numInputSamples = remainingSamples; +} + +/* Just copy from the array to the output buffer */ +// 拷贝数组到输出缓冲区 +static int copyToOutput( + sonicStream stream, + short *samples, + int numSamples) +{ + if(!enlargeOutputBufferIfNeeded(stream, numSamples)) { + return 0; + } + memcpy(stream->outputBuffer + stream->numOutputSamples*stream->numChannels, + samples, numSamples*sizeof(short)*stream->numChannels); + stream->numOutputSamples += numSamples; + return 1; +} + +/* Just copy from the input buffer to the output buffer. Return 0 if we fail to + resize the output buffer. Otherwise, return numSamples */ +// 仅仅把输入缓冲区中的数据拷贝到输出缓冲区中,返回转移了的采样点的个数 +// position表示偏移量 +static int copyInputToOutput( + sonicStream stream, + int position) +{ + int numSamples = stream->remainingInputToCopy; + // + if(numSamples > stream->maxRequired) { + numSamples = stream->maxRequired; + } + if(!copyToOutput(stream, stream->inputBuffer + position*stream->numChannels, + numSamples)) { + return 0; + } + // 剩余需要拷贝的输入缓冲区的采样点数 + stream->remainingInputToCopy -= numSamples; + return numSamples; +} + +/* Read 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) +{ + int numSamples = stream->numOutputSamples; + int remainingSamples = 0; + short *buffer; + int count; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + buffer = stream->outputBuffer; + count = numSamples*stream->numChannels; + while(count--) { + *samples++ = (*buffer++)/32767.0f; + } + if(remainingSamples > 0) { + memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, + remainingSamples*sizeof(short)*stream->numChannels); + } + stream->numOutputSamples = remainingSamples; + return numSamples; +} + +/* Read short data out of the stream. Sometimes no data will be available, and zero + is returned, which is not an error condition. */ +// 从流中读取short类型的数据,如果没有数据返回0 +int sonicReadShortFromStream( + sonicStream stream, + short *samples, + int maxSamples) +{ + int numSamples = stream->numOutputSamples; + int remainingSamples = 0; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + memcpy(samples, stream->outputBuffer, numSamples*sizeof(short)*stream->numChannels); + if(remainingSamples > 0) { + memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, + remainingSamples*sizeof(short)*stream->numChannels); + } + stream->numOutputSamples = remainingSamples; + return numSamples; +} + +/* Read unsigned char 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) +{ + int numSamples = stream->numOutputSamples; + int remainingSamples = 0; + short *buffer; + int count; + + if(numSamples == 0) { + return 0; + } + if(numSamples > maxSamples) { + remainingSamples = numSamples - maxSamples; + numSamples = maxSamples; + } + buffer = stream->outputBuffer; + count = numSamples*stream->numChannels; + while(count--) { + *samples++ = (char)((*buffer++) >> 8) + 128; + } + if(remainingSamples > 0) { + memmove(stream->outputBuffer, stream->outputBuffer + numSamples*stream->numChannels, + remainingSamples*sizeof(short)*stream->numChannels); + } + stream->numOutputSamples = remainingSamples; + return numSamples; +} + +/* 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) +{ + int maxRequired = stream->maxRequired; + int remainingSamples = stream->numInputSamples; + float speed = stream->speed/stream->pitch; + float rate = stream->rate*stream->pitch; + int expectedOutputSamples = stream->numOutputSamples + + (int)((remainingSamples/speed + stream->numPitchSamples)/rate + 0.5f); + + /* Add enough silence to flush both input and pitch buffers. */ + if(!enlargeInputBufferIfNeeded(stream, remainingSamples + 2*maxRequired)) { + return 0; + } + memset(stream->inputBuffer + remainingSamples*stream->numChannels, 0, + 2*maxRequired*sizeof(short)*stream->numChannels); + stream->numInputSamples += 2*maxRequired; + if(!sonicWriteShortToStream(stream, NULL, 0)) { + return 0; + } + /* Throw away any extra samples we generated due to the silence we added */ + if(stream->numOutputSamples > expectedOutputSamples) { + stream->numOutputSamples = expectedOutputSamples; + } + /* Empty input and pitch buffers */ + stream->numInputSamples = 0; + stream->remainingInputToCopy = 0; + stream->numPitchSamples = 0; + return 1; +} + +/* Return the number of samples in the output buffer */ +int sonicSamplesAvailable( + sonicStream stream) +{ + return stream->numOutputSamples; +} + +/* If skip is greater than one, average skip samples together and write them to + the down-sample buffer. If numChannels is greater than one, mix the channels + together as we down sample. */ +static void downSampleInput( + sonicStream stream, + short *samples, + int skip) +{ + int numSamples = stream->maxRequired/skip; + int samplesPerValue = stream->numChannels*skip; + int i, j; + int value; + short *downSamples = stream->downSampleBuffer; + + for(i = 0; i < numSamples; i++) { + value = 0; + for(j = 0; j < samplesPerValue; j++) { + value += *samples++; + } + value /= samplesPerValue; + *downSamples++ = value; + } +} + +/* Find the best frequency match in the range, and given a sample skip multiple. + For now, just find the pitch of the first channel. */ +static int findPitchPeriodInRange( + short *samples, + int minPeriod, + int maxPeriod, + int *retMinDiff, + int *retMaxDiff) +{ + int period, bestPeriod = 0, worstPeriod = 255; + short *s, *p, sVal, pVal; + unsigned long diff, minDiff = 1, maxDiff = 0; + int i; + + for(period = minPeriod; period <= maxPeriod; period++) { + diff = 0; + s = samples; + p = samples + period; + for(i = 0; i < period; i++) { + sVal = *s++; + pVal = *p++; + diff += sVal >= pVal? (unsigned short)(sVal - pVal) : + (unsigned short)(pVal - sVal); + } + /* Note that the highest number of samples we add into diff will be less + than 256, since we skip samples. Thus, diff is a 24 bit number, and + we can safely multiply by numSamples without overflow */ + /* if (bestPeriod == 0 || (bestPeriod*3/2 > period && diff*bestPeriod < minDiff*period) || + diff*bestPeriod < (minDiff >> 1)*period) {*/ + if (bestPeriod == 0 || diff*bestPeriod < minDiff*period) { + minDiff = diff; + bestPeriod = period; + } + if(diff*worstPeriod > maxDiff*period) { + maxDiff = diff; + worstPeriod = period; + } + } + *retMinDiff = minDiff/bestPeriod; + *retMaxDiff = maxDiff/worstPeriod; + return bestPeriod; +} + +/* At abrupt ends of voiced words, we can have pitch periods that are better + approximated by the previous pitch period estimate. Try to detect this case. */ +static int prevPeriodBetter( + sonicStream stream, + int period, + int minDiff, + int maxDiff, + int preferNewPeriod) +{ + if(minDiff == 0 || stream->prevPeriod == 0) { + return 0; + } + if(preferNewPeriod) { + if(maxDiff > minDiff*3) { + /* Got a reasonable match this period */ + return 0; + } + if(minDiff*2 <= stream->prevMinDiff*3) { + /* Mismatch is not that much greater this period */ + return 0; + } + } else { + if(minDiff <= stream->prevMinDiff) { + return 0; + } + } + return 1; +} + +/* Find the pitch period. This is a critical step, and we may have to try + multiple ways to get a good answer. This version uses Average Magnitude + Difference Function (AMDF). To improve speed, we down sample by an integer + factor get in the 11KHz range, and then do it again with a narrower + frequency range without down sampling */ +static int findPitchPeriod( + sonicStream stream, + short *samples, + int preferNewPeriod) +{ + int minPeriod = stream->minPeriod; + int maxPeriod = stream->maxPeriod; + int sampleRate = stream->sampleRate; + int minDiff, maxDiff, retPeriod; + int skip = 1; + int period; + + if(sampleRate > SONIC_AMDF_FREQ && stream->quality == 0) { + skip = sampleRate/SONIC_AMDF_FREQ; + } + if(stream->numChannels == 1 && skip == 1) { + period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, &minDiff, &maxDiff); + } else { + downSampleInput(stream, samples, skip); + period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod/skip, + maxPeriod/skip, &minDiff, &maxDiff); + if(skip != 1) { + period *= skip; + minPeriod = period - (skip << 2); + maxPeriod = period + (skip << 2); + if(minPeriod < stream->minPeriod) { + minPeriod = stream->minPeriod; + } + if(maxPeriod > stream->maxPeriod) { + maxPeriod = stream->maxPeriod; + } + if(stream->numChannels == 1) { + period = findPitchPeriodInRange(samples, minPeriod, maxPeriod, + &minDiff, &maxDiff); + } else { + downSampleInput(stream, samples, 1); + period = findPitchPeriodInRange(stream->downSampleBuffer, minPeriod, + maxPeriod, &minDiff, &maxDiff); + } + } + } + if(prevPeriodBetter(stream, period, minDiff, maxDiff, preferNewPeriod)) { + retPeriod = stream->prevPeriod; + } else { + retPeriod = period; + } + stream->prevMinDiff = minDiff; + stream->prevPeriod = period; + return retPeriod; +} + +/* Overlap two sound segments, ramp the volume of one down, while ramping the + other one from zero up, and add them, storing the result at the output. */ +static void overlapAdd( + int numSamples, + int numChannels, + short *out, + short *rampDown, + short *rampUp) +{ + short *o, *u, *d; + int i, t; + + for(i = 0; i < numChannels; i++) { + o = out + i; + u = rampUp + i; + d = rampDown + i; + for(t = 0; t < numSamples; t++) { +#ifdef SONIC_USE_SIN + float ratio = sin(t*M_PI/(2*numSamples)); + *o = *d*(1.0f - ratio) + *u*ratio; +#else + *o = (*d*(numSamples - t) + *u*t)/numSamples; +#endif + o += numChannels; + d += numChannels; + u += numChannels; + } + } +} + +/* Overlap two sound segments, ramp the volume of one down, while ramping the + other one from zero up, and add them, storing the result at the output. */ +static void overlapAddWithSeparation( + int numSamples, + int numChannels, + int separation, + short *out, + short *rampDown, + short *rampUp) +{ + short *o, *u, *d; + int i, t; + + for(i = 0; i < numChannels; i++) { + o = out + i; + u = rampUp + i; + d = rampDown + i; + for(t = 0; t < numSamples + separation; t++) { + if(t < separation) { + *o = *d*(numSamples - t)/numSamples; + d += numChannels; + } else if(t < numSamples) { + *o = (*d*(numSamples - t) + *u*(t - separation))/numSamples; + d += numChannels; + u += numChannels; + } else { + *o = *u*(t - separation)/numSamples; + u += numChannels; + } + o += numChannels; + } + } +} + +/* Just move the new samples in the output buffer to the pitch buffer */ +static int moveNewSamplesToPitchBuffer( + sonicStream stream, + int originalNumOutputSamples) +{ + int numSamples = stream->numOutputSamples - originalNumOutputSamples; + int numChannels = stream->numChannels; + + if(stream->numPitchSamples + numSamples > stream->pitchBufferSize) { + stream->pitchBufferSize += (stream->pitchBufferSize >> 1) + numSamples; + stream->pitchBuffer = (short *)realloc(stream->pitchBuffer, + stream->pitchBufferSize*sizeof(short)*numChannels); + if(stream->pitchBuffer == NULL) { + return 0; + } + } + memcpy(stream->pitchBuffer + stream->numPitchSamples*numChannels, + stream->outputBuffer + originalNumOutputSamples*numChannels, + numSamples*sizeof(short)*numChannels); + stream->numOutputSamples = originalNumOutputSamples; + stream->numPitchSamples += numSamples; + return 1; +} + +/* Remove processed samples from the pitch buffer. */ +static void removePitchSamples( + sonicStream stream, + int numSamples) +{ + int numChannels = stream->numChannels; + short *source = stream->pitchBuffer + numSamples*numChannels; + + if(numSamples == 0) { + return; + } + if(numSamples != stream->numPitchSamples) { + memmove(stream->pitchBuffer, source, (stream->numPitchSamples - + numSamples)*sizeof(short)*numChannels); + } + stream->numPitchSamples -= numSamples; +} + +/* Change the pitch. The latency this introduces could be reduced by looking at + past samples to determine pitch, rather than future. */ +static int adjustPitch( + sonicStream stream, + int originalNumOutputSamples) +{ + float pitch = stream->pitch; + int numChannels = stream->numChannels; + int period, newPeriod, separation; + int position = 0; + short *out, *rampDown, *rampUp; + + if(stream->numOutputSamples == originalNumOutputSamples) { + return 1; + } + if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) { + return 0; + } + while(stream->numPitchSamples - position >= stream->maxRequired) { + period = findPitchPeriod(stream, stream->pitchBuffer + position*numChannels, 0); + newPeriod = period/pitch; + if(!enlargeOutputBufferIfNeeded(stream, newPeriod)) { + return 0; + } + out = stream->outputBuffer + stream->numOutputSamples*numChannels; + if(pitch >= 1.0f) { + rampDown = stream->pitchBuffer + position*numChannels; + rampUp = stream->pitchBuffer + (position + period - newPeriod)*numChannels; + overlapAdd(newPeriod, numChannels, out, rampDown, rampUp); + } else { + rampDown = stream->pitchBuffer + position*numChannels; + rampUp = stream->pitchBuffer + position*numChannels; + separation = newPeriod - period; + overlapAddWithSeparation(period, numChannels, separation, out, rampDown, rampUp); + } + stream->numOutputSamples += newPeriod; + position += period; + } + removePitchSamples(stream, position); + return 1; +} + +/* Aproximate the sinc function times a Hann window from the sinc table. */ +static int findSincCoefficient(int i, int ratio, int width) { + int lobePoints = (SINC_TABLE_SIZE-1)/SINC_FILTER_POINTS; + int left = i*lobePoints + (ratio*lobePoints)/width; + int right = left + 1; + int position = i*lobePoints*width + ratio*lobePoints - left*width; + int leftVal = sincTable[left]; + int rightVal = sincTable[right]; + + return ((leftVal*(width - position) + rightVal*position) << 1)/width; +} + +/* Return 1 if value >= 0, else -1. This represents the sign of value. */ +static int getSign(int value) { + return value >= 0? 1 : 0; +} + +/* Interpolate the new output sample. */ +static short interpolate( + sonicStream stream, + short *in, + int oldSampleRate, + int newSampleRate) +{ + /* Compute N-point sinc FIR-filter here. Clip rather than overflow. */ + int i; + int total = 0; + int position = stream->newRatePosition*oldSampleRate; + int leftPosition = stream->oldRatePosition*newSampleRate; + int rightPosition = (stream->oldRatePosition + 1)*newSampleRate; + int ratio = rightPosition - position - 1; + int width = rightPosition - leftPosition; + int weight, value; + int oldSign; + int overflowCount = 0; + + for (i = 0; i < SINC_FILTER_POINTS; i++) { + weight = findSincCoefficient(i, ratio, width); + /* printf("%u %f\n", i, weight); */ + value = in[i*stream->numChannels]*weight; + oldSign = getSign(total); + total += value; + if (oldSign != getSign(total) && getSign(value) == oldSign) { + /* We must have overflowed. This can happen with a sinc filter. */ + overflowCount += oldSign; + } + } + /* It is better to clip than to wrap if there was a overflow. */ + if (overflowCount > 0) { + return SHRT_MAX; + } else if (overflowCount < 0) { + return SHRT_MIN; + } + return total >> 16; +} + +/* Change the rate. Interpolate with a sinc FIR filter using a Hann window. */ +static int adjustRate( + sonicStream stream, + float rate, + int originalNumOutputSamples) +{ + int newSampleRate = stream->sampleRate/rate; + int oldSampleRate = stream->sampleRate; + int numChannels = stream->numChannels; + int position = 0; + short *in, *out; + int i; + int N = SINC_FILTER_POINTS; + + /* Set these values to help with the integer math */ + while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) { + newSampleRate >>= 1; + oldSampleRate >>= 1; + } + if(stream->numOutputSamples == originalNumOutputSamples) { + return 1; + } + if(!moveNewSamplesToPitchBuffer(stream, originalNumOutputSamples)) { + return 0; + } + /* Leave at least N pitch sample in the buffer */ + for(position = 0; position < stream->numPitchSamples - N; position++) { + while((stream->oldRatePosition + 1)*newSampleRate > + stream->newRatePosition*oldSampleRate) { + if(!enlargeOutputBufferIfNeeded(stream, 1)) { + return 0; + } + out = stream->outputBuffer + stream->numOutputSamples*numChannels; + in = stream->pitchBuffer + position*numChannels; + for(i = 0; i < numChannels; i++) { + *out++ = interpolate(stream, in, oldSampleRate, newSampleRate); + in++; + } + stream->newRatePosition++; + stream->numOutputSamples++; + } + stream->oldRatePosition++; + if(stream->oldRatePosition == oldSampleRate) { + stream->oldRatePosition = 0; + if(stream->newRatePosition != newSampleRate) { + fprintf(stderr, + "Assertion failed: stream->newRatePosition != newSampleRate\n"); + exit(1); + } + stream->newRatePosition = 0; + } + } + removePitchSamples(stream, position); + return 1; +} + +/* Skip over a pitch period, and copy period/speed samples to the output */ +static int skipPitchPeriod( + sonicStream stream, + short *samples, + float speed, + int period) +{ + long newSamples; + int numChannels = stream->numChannels; + + if(speed >= 2.0f) { + newSamples = period/(speed - 1.0f); + } else { + newSamples = period; + stream->remainingInputToCopy = period*(2.0f - speed)/(speed - 1.0f); + } + if(!enlargeOutputBufferIfNeeded(stream, newSamples)) { + return 0; + } + overlapAdd(newSamples, numChannels, stream->outputBuffer + + stream->numOutputSamples*numChannels, samples, samples + period*numChannels); + stream->numOutputSamples += newSamples; + return newSamples; +} + +/* Insert a pitch period, and determine how much input to copy directly. */ +static int insertPitchPeriod( + sonicStream stream, + short *samples, + float speed, + int period) +{ + long newSamples; + short *out; + int numChannels = stream->numChannels; + + if(speed < 0.5f) { + newSamples = period*speed/(1.0f - speed); + } else { + newSamples = period; + stream->remainingInputToCopy = period*(2.0f*speed - 1.0f)/(1.0f - speed); + } + if(!enlargeOutputBufferIfNeeded(stream, period + newSamples)) { + return 0; + } + out = stream->outputBuffer + stream->numOutputSamples*numChannels; + memcpy(out, samples, period*sizeof(short)*numChannels); + out = stream->outputBuffer + (stream->numOutputSamples + period)*numChannels; + overlapAdd(newSamples, numChannels, out, samples + period*numChannels, samples); + stream->numOutputSamples += period + newSamples; + return newSamples; +} + +/* Resample as many pitch periods as we have buffered on the input. Return 0 if + we fail to resize an input or output buffer. */ +// 尽可能多的重采样输入缓冲区中的基音周期,如果成功返回非0 +static int changeSpeed( + sonicStream stream, + float speed) +{ + short *samples; + int numSamples = stream->numInputSamples; + int position = 0, period, newSamples; + int maxRequired = stream->maxRequired; + + /* printf("Changing speed to %f\n", speed); */ + if(stream->numInputSamples < maxRequired) { + return 1; + } + do { + // 流中剩余的采样点的个数 + if(stream->remainingInputToCopy > 0) { + // + newSamples = copyInputToOutput(stream, position); + position += newSamples; + } else { + samples = stream->inputBuffer + position*stream->numChannels; + period = findPitchPeriod(stream, samples, 1); + if(speed > 1.0) { + newSamples = skipPitchPeriod(stream, samples, speed, period); + position += period + newSamples; + } else { + newSamples = insertPitchPeriod(stream, samples, speed, period); + position += newSamples; + } + } + if(newSamples == 0) { + return 0; /* Failed to resize output buffer */ + } + } while(position + maxRequired <= numSamples); + removeInputSamples(stream, position); + return 1; +} + +/* Resample as many pitch periods as we have buffered on the input. Return 0 if + we fail to resize an input or output buffer. Also scale the output by the volume. */ +// 尽可能多的将输入缓冲区中的基音周期进行重采样,如果失败返回0,如果成功返回1。同时也scale了音量 +static int processStreamInput( + sonicStream stream) +{ + // 流中输出缓冲区中原有的采样点的数量 + int originalNumOutputSamples = stream->numOutputSamples; + // 速度 + float speed = stream->speed/stream->pitch; + float rate = stream->rate; + // 如果不用chordPitch + if(!stream->useChordPitch) { + rate *= stream->pitch; + } + // 改变速度 + if(speed > 1.00001 || speed < 0.99999) { + changeSpeed(stream, speed); + } else { + if(!copyToOutput(stream, stream->inputBuffer, stream->numInputSamples)) { + return 0; + } + stream->numInputSamples = 0; + } + if(stream->useChordPitch) { + if(stream->pitch != 1.0f) { + if(!adjustPitch(stream, originalNumOutputSamples)) { + return 0; + } + } + } else if(rate != 1.0f) { + if(!adjustRate(stream, rate, originalNumOutputSamples)) { + return 0; + } + } + if(stream->volume != 1.0f) { + /* Adjust output volume. */ + scaleSamples(stream->outputBuffer + originalNumOutputSamples*stream->numChannels, + (stream->numOutputSamples - originalNumOutputSamples)*stream->numChannels, + stream->volume); + } + return 1; +} + +/* Write floating point data to the input buffer and process it. */ +int sonicWriteFloatToStream( + sonicStream stream, + float *samples, + int numSamples) +{ + if(!addFloatSamplesToInputBuffer(stream, samples, numSamples)) { + return 0; + } + return processStreamInput(stream); +} + +/* Simple wrapper around sonicWriteFloatToStream that does the short to float + conversion for you. */ + // 向流中写入short类型的数据并进行处理 +int sonicWriteShortToStream( + sonicStream stream, + short *samples, + int numSamples) +{ + if(!addShortSamplesToInputBuffer(stream, samples, numSamples)) { + return 0; + } + // 处理输入流的数据 + return processStreamInput(stream); +} + +/* Simple wrapper around sonicWriteFloatToStream that does the unsigned char to float + conversion for you. */ +int sonicWriteUnsignedCharToStream( + sonicStream stream, + unsigned char *samples, + int numSamples) +{ + if(!addUnsignedCharSamplesToInputBuffer(stream, samples, numSamples)) { + return 0; + } + return processStreamInput(stream); +} + +/* This is a non-stream oriented interface to just change the speed of a sound sample */ +int sonicChangeFloatSpeed( + float *samples, + int numSamples, + float speed, + float pitch, + float rate, + float volume, + int useChordPitch, + int sampleRate, + int numChannels) +{ + sonicStream stream = sonicCreateStream(sampleRate, numChannels); + + sonicSetSpeed(stream, speed); + sonicSetPitch(stream, pitch); + sonicSetRate(stream, rate); + sonicSetVolume(stream, volume); + sonicSetChordPitch(stream, useChordPitch); + sonicWriteFloatToStream(stream, samples, numSamples); + sonicFlushStream(stream); + numSamples = sonicSamplesAvailable(stream); + sonicReadFloatFromStream(stream, samples, numSamples); + sonicDestroyStream(stream); + return numSamples; +} + +/* This is a non-stream oriented interface to just change the speed of a sound sample */ +int sonicChangeShortSpeed( + short *samples, + int numSamples, + float speed, + float pitch, + float rate, + float volume, + int useChordPitch, + int sampleRate, + int numChannels) +{ // 创建并初始化流 + sonicStream stream = sonicCreateStream(sampleRate, numChannels); + // 设置流的速度 + sonicSetSpeed(stream, speed); + // 设置流的音调 + sonicSetPitch(stream, pitch); + // 设置流的速率 + sonicSetRate(stream, rate); + // 设置流的音量 + sonicSetVolume(stream, volume); + // 设置 + sonicSetChordPitch(stream, useChordPitch); + // 向流中写入short类型的数据 + sonicWriteShortToStream(stream, samples, numSamples); + sonicFlushStream(stream); + numSamples = sonicSamplesAvailable(stream); + sonicReadShortFromStream(stream, samples, numSamples); + sonicDestroyStream(stream); + return numSamples; +} diff --git a/sonic.h b/sonic.h new file mode 100644 index 0000000..ef0c753 --- /dev/null +++ b/sonic.h @@ -0,0 +1,164 @@ +/* Sonic library + Copyright 2010 + Bill Cox + This file is part of the Sonic Library. + + This file is licensed under the Apache 2.0 license, and also placed into the public domain. + Use it either way, at your option. +*/ + +/* +The Sonic Library implements a new algorithm invented by Bill Cox for the +specific purpose of speeding up speech by high factors at high quality. It +generates smooth speech at speed up factors as high as 6X, possibly more. It is +also capable of slowing down speech, and generates high quality results +regardless of the speed up or slow down factor. For speeding up speech by 2X or +more, the following equation is used: + + newSamples = period/(speed - 1.0) + scale = 1.0/newSamples; + +where period is the current pitch period, determined using AMDF or any other +pitch estimator, and speed is the speedup factor. If the current position in +the input stream is pointed to by "samples", and the current output stream +position is pointed to by "out", then newSamples number of samples can be +generated with: + + out[t] = (samples[t]*(newSamples - t) + samples[t + period]*t)/newSamples; + +where t = 0 to newSamples - 1. + +For speed factors < 2X, the PICOLA algorithm is used. The above +algorithm is first used to double the speed of one pitch period. Then, enough +input is directly copied from the input to the output to achieve the desired +speed up factor, where 1.0 < speed < 2.0. The amount of data copied is derived: + + speed = (2*period + length)/(period + length) + speed*length + speed*period = 2*period + length + length(speed - 1) = 2*period - speed*period + length = period*(2 - speed)/(speed - 1) + +For slowing down speech where 0.5 < speed < 1.0, a pitch period is inserted into +the output twice, and length of input is copied from the input to the output +until the output desired speed is reached. The length of data copied is: + + length = period*(speed - 0.5)/(1 - speed) + +For slow down factors below 0.5, no data is copied, and an algorithm +similar to high speed factors is used. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Uncomment this to use sin-wav based overlap add which in theory can improve + sound quality slightly, at the expense of lots of floating point math. */ +/* #define SONIC_USE_SIN */ + +/* This specifies the range of voice pitches we try to match. + Note that if we go lower than 65, we could overflow in findPitchInRange */ +#define SONIC_MIN_PITCH 65 +#define SONIC_MAX_PITCH 400 + +/* These are used to down-sample some inputs to improve speed */ +#define SONIC_AMDF_FREQ 4000 + +struct sonicStreamStruct; +typedef struct sonicStreamStruct *sonicStream; + +/* For all of the following functions, numChannels is multiplied by numSamples + to determine the actual number of values read or returned. */ + +/* Create a sonic stream. Return NULL only if we are out of memory and cannot + allocate the stream. Set numChannels to 1 for mono, and 2 for stereo. */ +// 创建一个音频流,如果内存溢出不能创建流会返回NULL,numCHannels表示声道的个数,1为单声道,2为双声道 +sonicStream sonicCreateStream(int sampleRate, int numChannels); +/* Destroy the sonic stream. */ +// 销毁一个音频流 +void sonicDestroyStream(sonicStream stream); +/* Use this to write floating point data to be speed up or down into the stream. + Values must be between -1 and 1. Return 0 if memory realloc failed, otherwise 1 */ +// +int sonicWriteFloatToStream(sonicStream stream, float *samples, int numSamples); +/* Use this to write 16-bit data to be speed up or down into the stream. + Return 0 if memory realloc failed, otherwise 1 */ +int sonicWriteShortToStream(sonicStream stream, short *samples, int numSamples); +/* Use this to write 8-bit unsigned data to be speed up or down into the stream. + Return 0 if memory realloc failed, otherwise 1 */ +int sonicWriteUnsignedCharToStream(sonicStream stream, unsigned char *samples, int numSamples); +/* Use this to read floating point data out of the stream. Sometimes no data + will be available, and zero is returned, which is not an error condition. */ +int sonicReadFloatFromStream(sonicStream stream, float *samples, int maxSamples); +/* Use this to read 16-bit data out of the stream. Sometimes no data will + be available, and zero is returned, which is not an error condition. */ +int sonicReadShortFromStream(sonicStream stream, short *samples, int maxSamples); +/* Use this to read 8-bit unsigned data out of the stream. Sometimes no data will + be available, and zero is returned, which is not an error condition. */ +int sonicReadUnsignedCharFromStream(sonicStream stream, unsigned char *samples, int maxSamples); +/* Force the sonic stream to generate output using whatever data it currently + has. No extra delay will be added to the output, but flushing in the middle of + words could introduce distortion. */ +// 立即强制刷新流 +int sonicFlushStream(sonicStream stream); +/* Return the number of samples in the output buffer */ +// 返回输出缓冲中的采样点数目 +int sonicSamplesAvailable(sonicStream stream); +/* Get the speed of the stream. */ +// 得到音频流的速度 +float sonicGetSpeed(sonicStream stream); +/* Set the speed of the stream. */ +// 设置音频流的速度 +void sonicSetSpeed(sonicStream stream, float speed); +/* Get the pitch of the stream. */ +float sonicGetPitch(sonicStream stream); +/* Set the pitch of the stream. */ +void sonicSetPitch(sonicStream stream, float pitch); +/* Get the rate of the stream. */ +float sonicGetRate(sonicStream stream); +/* Set the rate of the stream. */ +void sonicSetRate(sonicStream stream, float rate); +/* Get the scaling factor of the stream. */ +float sonicGetVolume(sonicStream stream); +/* Set the scaling factor of the stream. */ +void sonicSetVolume(sonicStream stream, float volume); +/* Get the chord pitch setting. */ +int sonicGetChordPitch(sonicStream stream); +/* Set chord pitch mode on or off. Default is off. See the documentation + page for a description of this feature. */ +void sonicSetChordPitch(sonicStream stream, int useChordPitch); +/* Get the quality setting. */ +// 得到音频流的质量 +int sonicGetQuality(sonicStream stream); +/* Set the "quality". Default 0 is virtually as good as 1, but very much faster. */ +// 设置音频流的质量,默认的0的质量几乎和1的一样好,但是更快 +void sonicSetQuality(sonicStream stream, int quality); +/* Get the sample rate of the stream. */ +// 得到音频流的采样率 +int sonicGetSampleRate(sonicStream stream); +/* Set the sample rate of the stream. This will drop any samples that have not been read. */ +// 设置音频流的采样率 +void sonicSetSampleRate(sonicStream stream, int sampleRate); +/* Get the number of channels. */ +// 得到音频的声道数 +int sonicGetNumChannels(sonicStream stream); +/* Set the number of channels. This will drop any samples that have not been read. */ +// 设置音频流的声道数 +void sonicSetNumChannels(sonicStream stream, int numChannels); +/* This is a non-stream oriented interface to just change the speed of a sound + sample. It works in-place on the sample array, so there must be at least + speed*numSamples available space in the array. Returns the new number of samples. */ +// 这是一个非面向流的借口,只是改变声音采样的速率。它工作在采样数组内部, +//所以在数组内至少要有speed*numSampes大小的空间。返回值是新的采样点的数目 + +int sonicChangeFloatSpeed(float *samples, int numSamples, float speed, float pitch, + float rate, float volume, int useChordPitch, int sampleRate, int numChannels); +/* This is a non-stream oriented interface to just change the speed of a sound + sample. It works in-place on the sample array, so there must be at least + speed*numSamples available space in the array. Returns the new number of samples. */ +int sonicChangeShortSpeed(short *samples, int numSamples, float speed, float pitch, + float rate, float volume, int useChordPitch, int sampleRate, int numChannels); + +#ifdef __cplusplus +} +#endif diff --git a/toast.cpp b/toast.cpp new file mode 100644 index 0000000..7a97329 --- /dev/null +++ b/toast.cpp @@ -0,0 +1,91 @@ +#include "toast.h" + +#include +#include +#include +#include + +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(); +} diff --git a/toast.h b/toast.h new file mode 100644 index 0000000..1e624d5 --- /dev/null +++ b/toast.h @@ -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 +#include +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 diff --git a/urldialog.cpp b/urldialog.cpp new file mode 100644 index 0000000..fc07881 --- /dev/null +++ b/urldialog.cpp @@ -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(); +} diff --git a/urldialog.h b/urldialog.h new file mode 100644 index 0000000..1e2da84 --- /dev/null +++ b/urldialog.h @@ -0,0 +1,22 @@ +#ifndef URLDIALOG_H +#define URLDIALOG_H + +#include + +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 diff --git a/urldialog.ui b/urldialog.ui new file mode 100644 index 0000000..eb6b501 --- /dev/null +++ b/urldialog.ui @@ -0,0 +1,91 @@ + + + UrlDialog + + + + 0 + 0 + 653 + 157 + + + + Dialog + + + + + 220 + 100 + 301 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 30 + 50 + 591 + 31 + + + + + + + 40 + 20 + 231 + 16 + + + + 输入网络地址,比如http/rtmp + + + + + + + buttonBox + accepted() + UrlDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + UrlDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..f41ed60 --- /dev/null +++ b/util.cpp @@ -0,0 +1,3 @@ +#include "util.h" + + diff --git a/util.h b/util.h new file mode 100644 index 0000000..0d37e83 --- /dev/null +++ b/util.h @@ -0,0 +1,7 @@ +#ifndef UTIL_H +#define UTIL_H + + +//void ffmpeg_err2str(int err, char *) + +#endif // UTIL_H diff --git a/播放器.png b/播放器.png new file mode 100644 index 0000000..3627ee6 Binary files /dev/null and b/播放器.png differ