#include "FormatOut.hpp" #include #include #include #include extern "C"{ #include #include #include } #include "../log/log.hpp" #include "../configure/conf.hpp" #include "../property/VideoProp.hpp" #include "../data/CodedData.hpp" #include "../data/FrameData.hpp" #include "../../common/gpu/info.h" using namespace logif; namespace ffwrapper{ FormatOut::FormatOut() :ctx_(NULL) ,v_s_(NULL) ,enc_ctx_(NULL) ,sync_opts_(0) ,record_(false) ,fps_(0.0f) ,format_name_("mp4") {} FormatOut::~FormatOut() { clear(); } void FormatOut::clear(){ if(enc_ctx_){ avcodec_close(enc_ctx_); } if(ctx_){ closeResource(); avformat_free_context(ctx_); ctx_ = NULL; } v_s_ = NULL; sync_opts_ = 0; } FormatOut::FormatOut(VideoProp &prop, const char *filename, char *format_name/*=NULL*/) :FormatOut(){ bool flag = true; for (int i = 0; i < 2; ++i) { flag = open(filename, format_name); if(flag){ flag = openCodec(prop); if(!prop.gpuAccl()){ break; } if(!flag){ prop.gpu_acc_ = false; clear(); }else{ break; } } } if(!flag){ throw std::runtime_error("FormatOut Init Failed!"); } fps_ = prop.fps_; } /////////////////////////////////////////////////////////////////////// bool FormatOut::open(const char *filename, const char *format_name){ const int ret = avformat_alloc_output_context2(&ctx_, NULL, format_name, filename); if(ret < 0){ logIt("open %s failed:%s",filename, getAVErrorDesc(ret).c_str()); return false; } return true; } void FormatOut::configEncoder(VideoProp &prop){ enc_ctx_->codec_id = AV_CODEC_ID_H264; enc_ctx_->codec_type = AVMEDIA_TYPE_VIDEO; enc_ctx_->height = (prop.height_ & 0x01) ? prop.height_-1 : prop.height_; enc_ctx_->width = (prop.width_ & 0x01) ? prop.width_ - 1 : prop.width_; enc_ctx_->sample_aspect_ratio = prop.sample_aspect_ratio_; // v_s_->time_base.num = in->time_base.num; // v_s_->time_base.den = in->time_base.den; enc_ctx_->time_base.num = 1; enc_ctx_->time_base.den = prop.fps_; enc_ctx_->gop_size = 12; enc_ctx_->pix_fmt = AV_PIX_FMT_YUV420P; enc_ctx_->max_b_frames = 0; enc_ctx_->flags |= AV_CODEC_FLAG_QSCALE; enc_ctx_->bit_rate = prop.bit_rate_ * 1000; if(!prop.gpuAccl()){ enc_ctx_->bit_rate *= 0.75; } enc_ctx_->rc_min_rate = enc_ctx_->bit_rate / 2; enc_ctx_->rc_max_rate = enc_ctx_->bit_rate * 2 - enc_ctx_->rc_min_rate; enc_ctx_->rc_buffer_size = enc_ctx_->rc_max_rate * 10; } bool FormatOut::openCodec(VideoProp &prop){ AVOutputFormat *ofmt = ctx_->oformat; AVCodecID codec_id = AV_CODEC_ID_H264; AVCodec *codec = NULL; if(prop.gpuAccl()){ codec = avcodec_find_encoder_by_name("h264_nvenc"); if(!codec){ logIt("no support nvenc\n"); prop.gpu_acc_ = false; } } if(!prop.gpuAccl()){ codec = avcodec_find_encoder(codec_id); } if(!codec){ logIt("can't find encoder %s", codec->name); return false; } logIt("use encoder %s", codec->name); v_s_ = avformat_new_stream(ctx_, codec); enc_ctx_ = avcodec_alloc_context3(codec); configEncoder(prop); if(prop.gpuAccl()){ av_opt_set(enc_ctx_->priv_data, "preset", "llhp", 0); int idle_gpu = gpu::getGPU(120); if (prop.gpu_index_ > -1){ idle_gpu = prop.gpu_index_; } if(idle_gpu < 0){ logIt("NO GPU RESOURCE TO ENCODE"); return false; } av_opt_set_int(enc_ctx_->priv_data, "gpu", idle_gpu, 0); printf("ENCODER USE GPU %d\n", idle_gpu); }else{ av_opt_set(enc_ctx_->priv_data, "preset", prop.preset().c_str(), 0); } av_opt_set(enc_ctx_->priv_data, "tune", "zerolatency", 0); av_opt_set(enc_ctx_->priv_data, "profile", "baseline", 0); int err =avcodec_open2(enc_ctx_, codec, NULL); if( err< 0) { logIt("can't open output codec: %s", getAVErrorDesc(err).c_str()); return false; } err = avcodec_parameters_from_context(v_s_->codecpar, enc_ctx_); if (err < 0) { logIt("can't avcodec_parameters_from_context: %s", getAVErrorDesc(err).c_str()); return false; } ofmt->video_codec = codec_id; if(ofmt->flags & AVFMT_GLOBALHEADER) { enc_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } return true; } const AVCodecContext *FormatOut::getCodecContext()const{ return enc_ctx_; } int FormatOut::encode(AVPacket &pkt, AVFrame *frame){ AVStream *out = getStream(); frame->quality = enc_ctx_->global_quality; frame->pict_type = AV_PICTURE_TYPE_NONE; pkt.data = NULL; pkt.size = 0; int ret = avcodec_send_frame(enc_ctx_, frame); if(ret < 0){ logIt("avcodec_send_frame failed : %s", getAVErrorDesc(ret).c_str()); return -1; } while(ret >= 0){ ret = avcodec_receive_packet(enc_ctx_, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { break; }else if (ret < 0) { logIt("avcodec_receive_packet : %s", getAVErrorDesc(ret).c_str()); return -1; }else{ if(pkt.pts == AV_NOPTS_VALUE && !(enc_ctx_->codec->capabilities & AV_CODEC_CAP_DELAY)) { pkt.pts = sync_opts_++; } av_packet_rescale_ts(&pkt, enc_ctx_->time_base, out->time_base); // printf("pkt pts: %lld\n", pkt.pts); return 1; } } return 0; } int FormatOut::encode(std::shared_ptr &data, std::shared_ptr &frame_data){ AVStream *out = getStream(); AVCodecContext *enc_ctx = out->codec; data->refExtraData(enc_ctx->extradata, enc_ctx->extradata_size); AVPacket &pkt(data->getAVPacket()); AVFrame *frame = frame_data->getAVFrame(); return encode(pkt, frame); } int FormatOut::encode(std::shared_ptr &data,AVFrame *frame){ AVStream *out = getStream(); AVCodecContext *enc_ctx = out->codec; data->refExtraData(enc_ctx->extradata, enc_ctx->extradata_size); AVPacket &pkt(data->getAVPacket()); return encode(pkt, frame); } ////////////////////////////////////////////////////////////////////////// FormatOut::FormatOut(AVStream *in, const char *format_name) :FormatOut(){ format_name_ = format_name; if(in->r_frame_rate.num >=1 && in->r_frame_rate.den >= 1){ fps_ = av_q2d(in->r_frame_rate); }else if(in->avg_frame_rate.num >=1 && in->avg_frame_rate.den >= 1){ fps_ = av_q2d(in->avg_frame_rate); } } bool FormatOut::copyCodecFromIn(AVStream *in){ v_s_ = avformat_new_stream(ctx_, in->codec->codec); if(!v_s_){ return false; } int ret = avcodec_copy_context(v_s_->codec, in->codec); if (ret < 0){ logIt("can't copy codec from in error:%s", getAVErrorDesc(ret).c_str()); return false; } if (ctx_->oformat->flags & AVFMT_GLOBALHEADER) { v_s_->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } return true; } bool FormatOut::openResource(const char *filename, const int flags){ if((ctx_->flags & AVFMT_NOFILE) != AVFMT_NOFILE){ const int err = avio_open2(&ctx_->pb, filename, flags, NULL, NULL); if(err < 0) { logIt("can't save to %s error:%s",filename, getAVErrorDesc(err).c_str()); return false; } strcpy(&ctx_->filename[0], filename); } return true; } bool FormatOut::closeResource(){ if(record_){ return avio_close(ctx_->pb) == 0; } return true; } bool FormatOut::JustWriter(AVStream *in, const char *filename){ if(ctx_){ clear(); } bool flag = open(NULL, format_name_.c_str()); flag = copyCodecFromIn(in) && flag; if(!flag){ logIt("FormatOut JustWriter error from in"); return false; } flag = openResource(filename, 2); if(flag){ AVDictionary *avdic = NULL; char option_key[]="movflags"; char option_value[]="frag_keyframe+empty_moov"; av_dict_set(&avdic,option_key,option_value,0); flag = writeHeader(&avdic); av_dict_free(&avdic); } return flag; } bool FormatOut::EncodeWriter(const char *filename){ auto flag = openResource(filename, 2); if(flag){ AVDictionary *avdic = NULL; char option_key[]="movflags"; char option_value[]="frag_keyframe+empty_moov"; av_dict_set(&avdic,option_key,option_value,0); flag = writeHeader(&avdic); av_dict_free(&avdic); } return false; } bool FormatOut::endWriter(){ auto flag = writeTrailer(); record_ = false; return flag; } const char* FormatOut::getFileName() const{ return ctx_->filename; } ////////////////////////////////////////////////////////////////////////////////////////////////// bool FormatOut::writeHeader(AVDictionary **options/* = NULL*/){ const int ret = avformat_write_header(ctx_, options); if(ret < 0){ logIt("write header to file failed:%s", getAVErrorDesc(ret).c_str()); return false; } record_ = true; return true; } void FormatOut::adjustPTS(AVPacket &pkt, const int64_t &frame_cnt){ int64_t time_stamp = frame_cnt; pkt.pos = -1; pkt.stream_index = 0; //Write PTS AVRational time_base = getStream()->time_base; AVRational time_base_q = { 1, AV_TIME_BASE }; //Duration between 2 frames (us) int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / fps_); //内部时间戳 //Parameters pkt.pts = av_rescale_q(time_stamp*calc_duration, time_base_q, time_base); pkt.dts = pkt.pts; pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base)); } bool FormatOut::writeFrame(AVPacket &pkt, const int64_t &frame_cnt, bool interleaved/* = true*/){ adjustPTS(pkt, frame_cnt); return writeFrame2(pkt, interleaved); } bool FormatOut::writeFrame2(AVPacket &pkt, bool interleaved){ int ret = 0; if(interleaved) ret = av_interleaved_write_frame(ctx_, &pkt); else { // returns 1 if flushed and there is no more data to flush ret = av_write_frame(ctx_, &pkt); } if(ret < 0) { logIt("write packet to file failed:%s", getAVErrorDesc(ret).c_str()); return false; } return true; } bool FormatOut::writeTrailer(){ const int ret = av_write_trailer(ctx_); if(ret != 0) { logIt("write trailer to file failed:%s", getAVErrorDesc(ret).c_str()); return false; } return true; } }