#include "recorder.hpp" #include "sole.hpp" #include #include #include #include extern "C"{ #include } #include "../ffmpeg/format/FormatIn.hpp" #include "../ffmpeg/format/FormatOut.hpp" #include "../ffmpeg/data/CodedData.hpp" #include "../ffmpeg/log/log.hpp" using namespace logif; using namespace ffwrapper; namespace cffmpeg_wrap{ namespace buz{ Recorder::Recorder(FormatIn *in, const std::string &id) :in_(in) ,out_(NULL) ,maxduration(30 * 25) ,minduration(10 * 25) ,fp_(NULL) ,stop_recorder_(false) ,id_(id) ,id_frame_(0) ,id_frame_in_file_(0) ,file_path_("") ,func_rec_info_(nullptr) ,thrd_(nullptr) ,audio_(false) ,end_frame_(0) ,v_cur_frame_(0) ,a_cur_frame_(0) ,error_occured_(false) ,last_rec_id_(-1) { if (in){ maxduration = 30 * in->getFPS(); minduration = 10 * in->getFPS(); } } Recorder::~Recorder(){ try { if (thrd_){ if (!stop_recorder_.load()){ stop_recorder_.store(true); cv_.notify_one(); } thrd_->join(); // logIt("REC THREAD JOINED, QUIT!!!, %s", id_.c_str()); } } catch(const std::exception& e) { logIt("RECODER DESTRUCTOR EXCEPTION: ", e.what()); } if (fp_) { fclose(fp_); fp_ = NULL; } } int Recorder::init_write_h264(const bool audio){ if (out_) { delete out_; } out_ = new FormatOut(in_->getFPS(), "mp4"); int pid = getpid(); std::string filename(sole::uuid4().base62() + "-" + std::to_string(pid) + ".mp4"); file_path_ = dir_ + "/" + filename; std::string backup_dir("./video"); size_t pos = dir_.rfind("/"); if (pos != std::string::npos){ backup_dir = dir_.substr(0, pos); } auto v = in_->getStream(AVMEDIA_TYPE_VIDEO); if (!v){ return -2; } AVStream *a = in_->getStream(AVMEDIA_TYPE_AUDIO); if (!audio){ a = NULL; } bool ret = out_->JustWriter(v, a, file_path_.c_str()); if (ret){ logIt("start record h264 file: %s", file_path_.c_str()); return 0; }else{ mkdir(backup_dir.c_str(), 0777); file_path_ = backup_dir + "/" + filename; logIt("failed in dir %s, try file %s to start record file", dir_.c_str(), file_path_.c_str()); ret = out_->JustWriter(v, a, file_path_.c_str()); if (ret){ logIt("start record h264 file: %s", file_path_.c_str()); return 0; } } logIt("failed to start record: %s", file_path_.c_str()); return -1; } int Recorder::init_write_hevc(const bool audio){ if (fp_){ fclose(fp_); } int pid = getpid(); std::string filename(sole::uuid4().base62() + "-" + std::to_string(pid) + ".hevc"); file_path_ = dir_ + "/" + filename; std::string backup_dir("./video"); size_t pos = dir_.rfind("/"); if (pos != std::string::npos){ backup_dir = dir_.substr(0, pos); } fp_ = fopen(file_path_.c_str(), "wb"); if (!fp_){ mkdir(backup_dir.c_str(), 0777); file_path_ = backup_dir + "/" + filename; logIt("failed in dir %s, try file %s to start record hevc file", dir_.c_str(), file_path_.c_str()); fp_ = fopen(file_path_.c_str(), "wb"); if (!fp_){ logIt("failed start record hevc file: %s", file_path_.c_str()); return -1; } } logIt("start record hevc file: %s", file_path_.c_str()); return 0; } int Recorder::init_writer(const bool audio){ if(!in_){ logIt("init_writer FormatIn not init"); return -1; } if (in_->IsHEVC()){ return init_write_hevc(audio); }else{ return init_write_h264(audio); } return -2; } //////////////////////// int Recorder::write_h264(const CPacket &pkt){ //reader failed, break stream if(!pkt.data){ return -1; } if (v_cur_frame_ == end_frame_){ return 1; } AVPacket &op = pkt.data->getAVPacket(); if (!audio_ && in_->isAudioPkt(&op)) { return 0; } AVPacket np(op); av_copy_packet(&np, &op); int64_t cur = v_cur_frame_; if (in_->isVideoPkt(&np)){ if(pkt.v_id == id_frame_){ id_frame_in_file_ = v_cur_frame_; } v_cur_frame_++; }else if (in_->isAudioPkt(&np)) { cur = a_cur_frame_++; } auto ret = out_->writeFrame(&np, cur); av_packet_unref(&np); if (!ret) return -1; // logIt("WRITE FRAME ID: %d, RECORD ID: %d", pkt.id, id_frame_); return 0; } int Recorder::write_hevc(const CPacket &pkt){ if (!fp_){ logIt("write hevc packet error, file not open"); return -1; } if (v_cur_frame_ == end_frame_){ return 1; } AVPacket &op = pkt.data->getAVPacket(); if (in_->isAudioPkt(&op)) { return 0; } if (op.data == NULL){ logIt("hevc avpacket data null"); return 0; } if (in_->isVideoPkt(&op)){ if(pkt.v_id == id_frame_){ id_frame_in_file_ = v_cur_frame_; } v_cur_frame_++; } fwrite(op.data, op.size, 1, fp_); return 0; } int Recorder::write_correctly(const CPacket &pkt){ if (in_->IsHEVC()){ return write_hevc(pkt); } return write_h264(pkt); } int Recorder::end_write_h264(){ if (!out_) return -1; out_->endWriter(); if (out_){ delete out_; out_ = NULL; } return 0; } int Recorder::end_write_hevc(){ if (fp_){ fclose(fp_); fp_ = NULL; } std::string hevc_file(file_path_); auto pos = file_path_.rfind(".hevc"); if (pos != std::string::npos){ file_path_ = file_path_.substr(0, pos) + ".mp4"; logIt("mux hevc real file : %s", file_path_.c_str()); } FILE *fp = fopen(hevc_file.c_str(), "rb"); if (!fp) return 0; int ret = mux_hevc(fp, file_path_.c_str()); fclose(fp); if (ret == 0){ if (remove(hevc_file.c_str()) != 0){ logIt("mux hevc remove file %s failed", hevc_file.c_str()); } }else{ logIt("mux hevc to mp4 error, use raw hevc"); file_path_ = hevc_file; } return 0; } static int read_buffer(void *opaque, uint8_t *buf, int buf_size){ FILE *fp_open = (FILE*)opaque; if (!fp_open) logIt("mux hevc open file error"); if(!feof(fp_open)){ int true_size=fread(buf,1,buf_size,fp_open); return true_size; }else{ return -1; } } int Recorder::mux_hevc(FILE *fp, const char *outfile){ if (!fp) { logIt("mux hevc file handle is null"); return -1; } std::unique_ptr in(nullptr); int tryTime = 0; while(true){ std::unique_ptr tmp(new FormatIn(false)); auto ret = tmp->openWithCustomIO(fp, read_buffer); if (ret == 0){ in = std::move(tmp); break; } usleep(10000); if (tryTime++ > 100){ logIt("mux hevc try %d time to open custom io %s, failed", tryTime, outfile); return -2; } } if (in->open(NULL, NULL) < 0){ logIt("mux hevc open stream error"); return -3; } std::unique_ptr out(new FormatOut(in_->getFPS(), "mp4")); auto v = in->getStream(AVMEDIA_TYPE_VIDEO); if (!v){ logIt("mux hevc file can't find video stream"); return -5; } if (out->JustWriter(v, NULL, outfile)){ logIt("mux hevc start record file: %s", outfile); } int64_t id = 0; while(true){ AVPacket pkt; if (in->readPacket(&pkt) != 0){ logIt("mux hevc read packet error, id: %lld", id); break; } out->writeFrame(&pkt, id); // logIt("read frame: %d", id); av_packet_unref(&pkt); id++; } out->endWriter(); return 0; } void Recorder::end_writer(){ if (in_->IsHEVC()){ end_write_hevc(); }else{ end_write_h264(); } logIt("finished record : %s frames: %d, frame in file id: %d", file_path_.c_str(), end_frame_, id_frame_in_file_); { std::lock_guard l(mutex_pkt_); list_pkt_.clear(); } if(func_rec_info_){ func_rec_info_(id_,id_frame_in_file_, file_path_); } } void Recorder::run_thread(){ while(!stop_recorder_.load()){ std::list pkts; { std::unique_lock locker(mutex_pkt_); int sec = minduration/50; if (in_) sec = minduration/in_->getFPS()/2; auto status = cv_.wait_for(locker, std::chrono::seconds(sec), [&]{ return !list_pkt_.empty() || stop_recorder_.load(); }); if (!status || stop_recorder_.load()){ error_occured_ = !status; break; } list_pkt_.swap(pkts); } int ret = 0; for(auto &i : pkts){ ret = write_correctly(i); if (ret != 0){ break; } } if (ret != 0){ break; } } stop_recorder_.store(true); end_writer(); list_pkt_.clear(); } int Recorder::Run(const char* output, const int mind, const int maxd, const bool audio){ bool a = audio; if (in_->IsHEVC()) a = false; dir_ = output; int ret = init_writer(a); if(ret != 0){ logIt("recorder init writer error"); return -1; } double fps = in_->getFPS(); if(fps > 1.0){ maxduration = fps * maxd; minduration = fps * mind; end_frame_ = minduration; } audio_ = a; logIt("minduration %d maxduration %d", minduration, maxduration); thrd_.reset(new std::thread([&]{ run_thread(); })); //.detach(); return 0; } int Recorder::FireRecorder(const int64_t &id){ if (stop_recorder_.load()) return -1; if(id_frame_ == 0){ id_frame_ = id; std::lock_guard locker(mutex_pkt_); if (list_pkt_.size() > end_frame_){ end_frame_ = list_pkt_.size() + minduration/2; if (end_frame_ > maxduration) end_frame_ = maxduration; } }else if(v_cur_frame_ > minduration/2 && end_frame_ < maxduration){ logIt("cur frame: %d, end frame: %d, duration: [%d-%d]", v_cur_frame_, end_frame_, minduration, maxduration); end_frame_ = end_frame_ + minduration / 2; if(end_frame_ > maxduration){ end_frame_ = maxduration; } } return 0; } int Recorder::PushPacket(std::list &lst){ if (stop_recorder_.load()) return 0; std::lock_guard locker(mutex_pkt_); // 没有开始录制 if (last_rec_id_ < 0){ logIt("last rec id is 0 cache size: %ld", lst.size()); for (auto &i : lst){ // 从第一个非音频关键帧开始 if (last_rec_id_ < 0){ if (!in_->isVideoPkt(&i.data->getAVPacket())){ continue; } if (!(i.data->getAVPacket().flags & AV_PKT_FLAG_KEY)){ continue; } } last_rec_id_ = i.id; list_pkt_.push_back(i); } }else{ for(auto &i : lst){ if (i.id > last_rec_id_){ list_pkt_.push_back(i); last_rec_id_++; } } } cv_.notify_one(); return list_pkt_.size(); } int Recorder::StartWritePacket(std::list &lst, const int64_t &id, const int start, const int end){ if (stop_recorder_.load()) return 0; // 第一次录像,设置触发帧id id_frame_ = id; if (start < 0) { logIt("start write packet [%d-%d] in pkt size: %d, frame id: %lld, " "cur frame: %d, end frame: %d, duration: [%d-%d], last rec id: %lld", start, end, lst.size(), id_frame_, v_cur_frame_, end_frame_, minduration, maxduration, last_rec_id_); return -1; } std::lock_guard locker(mutex_pkt_); // 将传入的所有packets保存如缓存 int index = -1; for (auto &p : lst){ index++; if (index < start) continue; list_pkt_.push_back(p); if (index == end){ last_rec_id_ = p.id; break; } } logIt("start write packet [%d-%d] in pkt size: %d, frame id: %lld, " "cur frame: %d, end frame: %d, duration: [%d-%d], last rec id: %lld", start, end, lst.size(), id_frame_, v_cur_frame_, end_frame_, minduration, maxduration, last_rec_id_); // maybe_dump_gop(); cv_.notify_one(); return list_pkt_.size(); } void Recorder::maybe_dump_gop(){ while (list_pkt_.size() > maxduration) { list_pkt_.pop_front(); while(!list_pkt_.empty()){ auto &i = list_pkt_.front(); if (!(i.data->getAVPacket().flags & AV_PKT_FLAG_KEY)){ list_pkt_.pop_front(); }else{ break; } } } } }// end clase }// end namespace