#include "HxDevice.h" #include "HxTrace.h" #include "HxDisk.h" #include "HxBroadcast.h" HxDevice::HxDevice(int channel, bool secondary, int millisecond) : HxThread(millisecond) { this->channel = channel; this->secondary = secondary; this->uuid = QUuid::createUuid(); } void HxDevice::startup(QJsonObject object) { code = HxJson::to_string(object, "code"); drive_threshold = HxJson::to_int(object, "drive_threshold"); recording_split_time = HxJson::to_int(object, "recording_split_time"); disks.clear(); foreach(QString d, HxJson::to_string_list(object, "disks", ";")) { if (HxDisk::exist(d)) { disks.append(d); } } auto array = HxJson::to_string_list(object, QString("channel_%1").arg(channel, 2, 10, QLatin1Char('0')), ";"); auto type = array[2].toUpper(); auto address = array[3]; auto port = array[4].toInt(); auto enable = QVariant(array[7]).toBool(); auto record = QVariant(array[8]).toBool(); if (enable && record) { if (type == "LION" || type == "HIK") rtspurl = QString("rtsp://admin:hik12345@%1:%2/h264/ch1/%3/av_stream").arg(address).arg(port).arg(!secondary ? "main" : "sub"); else if (type == "XM") rtspurl = QString("rtsp://%1:%2/user=admin&password=&channel=0&stream=%3.sdp?").arg(address).arg(port).arg(!secondary ? 0 : 1); else if (type == "XIANGFEI") rtspurl = QString("rtsp://%1:%2/stander/livestream/0/%3").arg(address).arg(port).arg(!secondary ? 0 : 1); else if (type == "BSM") rtspurl = QString("rtsp://%1:%2/%3").arg(address).arg(port).arg(!secondary ? 11 : 12); /* 创建 视频读取线程 */ HxTask::run(this, &HxDevice::frame_read_process, 5000, uuid); start(); } } void HxDevice::frame_read_process() { int error = 0; AVDictionary* avdic = nullptr; /* 减少卡顿或者花屏现象,相当于增加或扩大了缓冲区,给予编码和发送足够的时间 */ av_dict_set(&avdic, "buffer_size", "1024000", 0); // /* 减少采集缓存 */ // av_dict_set(&avdic, "preset", "fast", 0); // av_dict_set(&avdic, "tune", "zerolatency", 0); // /* 减少音频采集sampels数量 */ // av_dict_set(&avdic, "audio_buffer_size","30", 0); av_dict_set(&avdic, "stimeout", "500000", 0); av_dict_set(&avdic, "max_delay", "500000", 0); av_dict_set(&avdic, "rtsp_transport", "tcp", 0); avformat_network_init(); ifmt_ctx = avformat_alloc_context(); error = avformat_open_input(&ifmt_ctx, rtspurl.toUtf8().data(), nullptr, &avdic); av_dict_free(&avdic); if (error != 0) { avformat_free_context(ifmt_ctx); HxTrace::debug_write_line("videolivestream", QString("address=%1, avformat_open_input failed, errorcode=%2").arg(rtspurl).arg(error)); return; } if (avformat_find_stream_info(ifmt_ctx, nullptr) < 0) { avformat_close_input(&ifmt_ctx); avformat_free_context(ifmt_ctx); HxTrace::debug_write_line("videolivestream", QString("address=%1, avformat_find_stream_info failed").arg(rtspurl)); return; } av_dump_format(ifmt_ctx, 0, rtspurl.toUtf8().data(), 0); auto video_stream_index = -1; for (uint i = 0; i < ifmt_ctx->nb_streams; i++) { if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // video_fps = ifmt_ctx->streams[i]->avg_frame_rate.num; // video_width = ifmt_ctx->streams[i]->codecpar->width; // video_height = ifmt_ctx->streams[i]->codecpar->height; video_stream_index = static_cast(i); break; } } if (video_stream_index == -1) { avformat_close_input(&ifmt_ctx); avformat_free_context(ifmt_ctx); HxTrace::debug_write_line("videolivestream", QString("address=%1, not found video stream").arg(rtspurl)); return; } while (true) { AVPacket packet; if (av_read_frame(ifmt_ctx, &packet) < 0) { stream_disconnect = true; break; } mutex.lock(); frames.append(HxVideoFrame(QDateTime::currentDateTime(), &packet)); mutex.unlock(); av_packet_unref(&packet); msleep(10); } if (ifmt_ctx != nullptr) { avformat_close_input(&ifmt_ctx); avformat_free_context(ifmt_ctx); ifmt_ctx = nullptr; } } void HxDevice::create_recording_file(HxVideoFrame frame) { /* 查找存储介质 */ QString root_path; foreach(QString path, disks) { auto freeSize = HxDisk::free_size(path); if (freeSize > drive_threshold) { root_path = path; break; } } if (root_path.isNull()) { build_video_log(tr("录像中断"), tr("存储介质异常")); return; } video_record.reset(); video_record.build(channel, frame.time, root_path, code); //需要写日志 if (!video_writer.open(ifmt_ctx, video_record.path)) { video_writer.close(); build_video_log(tr("录像中断"), video_writer.error()); } else { //设置录像标志位 video_record.status = RecordStatus::Recording; /* 发送录像创建通知 */ REPLACE_RECORD_LOG(video_record); build_video_log(tr("录像启动")); } } void HxDevice::close_recording_file(void) { if (video_record.status == RecordStatus::Recording) { video_writer.close(); video_record.status = RecordStatus::Recorded; video_record.duration = get_video_duration(video_record.path); video_record.end_time = QDateTime::currentDateTime(); REPLACE_RECORD_LOG(video_record); } } void HxDevice::write_recording_data(HxVideoFrame frame) { /* 判断是否是I帧 */ if (frame.packet->flags & AV_PKT_FLAG_KEY) { /* 当前状态: 未录像 */ if (video_record.status != RecordStatus::Recording) { create_recording_file(frame); } else { /* 判断录像时长 */ if (frame.time >= video_record.start_time.addSecs((int)recording_split_time * 60 + 1)) { HxTrace::debug_write_line("HxVideoCaptor", QString("Record PackTime fileName = %1 , %2 - %3").arg(video_record.name, video_record.start_time.toString("yyyy-MM-dd HH:mm:ss"), frame.time.toString("yyyy-MM-dd HH:mm:ss"))); /* 关闭录像 */ close_recording_file(); /* 重新打开录像 */ create_recording_file(frame); } } } /* 当前状态: 正在录像 */ if (video_record.status == RecordStatus::Recording) { video_writer.send(frame.packet); /* 特殊情况: 1. 判断文件是否被删除 */ if(!QFile::exists(video_record.path)) { /* 关闭录像 */ close_recording_file(); /* 将错误写日志 */ build_video_log(tr("录像中断"), tr("文件异常")); /* 重新开始录像 */ video_record.reset(); } } } int HxDevice::get_video_duration(QString path) { /* 始化网络设备 */ avformat_network_init(); /* 初始化 */ AVFormatContext* ifmt_ctx = avformat_alloc_context(); /* 打开输入文件 */ if (avformat_open_input(&ifmt_ctx, path.toUtf8().data(), nullptr, nullptr) < 0) return 0; /* 获取流信息 */ if (avformat_find_stream_info(ifmt_ctx, nullptr) < 0) { avformat_close_input(&ifmt_ctx); return -1; } /* 这里获取的是微秒,需要转成秒 */ int64_t tduration = ifmt_ctx->duration; /* 打开文件流后,需要关闭,否则会一直占用视频文件,无法进行视频文件的后续操作 */ avformat_close_input(&ifmt_ctx); /* 销毁 */ avformat_free_context(ifmt_ctx); return tduration / 1000000; } void HxDevice::build_video_log(QString title, QString data) { auto message = title + data; if (status_message != message) { status_message = message; WRITE_VIDEO_LOG(channel, secondary ? tr("副盘") : tr("主盘") + title, data); } } void HxDevice::action() { while (!frames.isEmpty()) { mutex.lock(); auto frame = frames.dequeue(); mutex.unlock(); write_recording_data(frame); frame.free(); msleep(0); } if (stream_disconnect && frames.isEmpty()) { close_recording_file(); build_video_log(tr("录像中断"), tr("摄像头断开连接")); stream_disconnect = false; } } void HxDevice::continue_with() { close_recording_file(); build_video_log(tr("录像停止")); }