339 lines
9.0 KiB
C++
339 lines
9.0 KiB
C++
#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 _channel = array[5].toInt();
|
|
auto enable = QVariant(array[6]).toBool();
|
|
auto record = QVariant(array[7]).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);
|
|
else if (type == "AHV")
|
|
rtspurl = QString("rtsp://%1:%2/dev/video%3").arg(address).arg(port).arg(_channel);
|
|
|
|
/* 创建 视频读取线程 */
|
|
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", "+udp+tcp", 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<int>(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("录像停止"));
|
|
}
|