HxVideoCaptor/HxDevice.cpp

335 lines
8.8 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 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<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("录像停止"));
}