#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 "../../common/gpu/info.h" using namespace logif; namespace ffwrapper{ FormatOut::FormatOut() :ctx_(NULL) ,v_idx_(-1) ,a_idx_(-1) ,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; } 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); AVStream *v = avformat_new_stream(ctx_, codec); v_idx_ = 0; 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->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; } AVStream *FormatOut::getStream(){ if (v_idx_ == -1) return NULL; return ctx_->streams[v_idx_]; } 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 0; } } return -2; } ////////////////////////////////////////////////////////////////////////// FormatOut::FormatOut(const double fps, const char *format_name) :FormatOut(){ format_name_ = format_name; fps_ = fps; } bool FormatOut::openResource(const char *filename, const int flags){ if((ctx_->oformat->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::copyCodecFromIn(std::vector in){ for (int i = 0; i < in.size(); i++){ AVStream *in_stream = in[i]; AVStream *out_stream = avformat_new_stream(ctx_, in_stream->codec->codec); if(!out_stream) { logIt("Failed allocating output stream.\n"); return false; } //将输出流的编码信息复制到输入流 auto ret = avcodec_copy_context(out_stream->codec, in_stream->codec); if(ret<0) { logIt("Failed to copy context from input to output stream codec context\n"); return false; } if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ v_idx_ = i; logIt("copy video from instream"); out_stream->codecpar->codec_tag = out_stream->codec->codec_tag = 0; if(ctx_->oformat->flags & AVFMT_GLOBALHEADER) out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } if (in_stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){ logIt("copy audio from instream"); a_idx_ = i; out_stream->codecpar->codec_tag = out_stream->codec->codec_tag = 0; if(ctx_->oformat->flags & AVFMT_GLOBALHEADER) out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } } in_streams_ = in; return true; } bool FormatOut::JustWriter(std::vector 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(); closeResource(); 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){ if (pkt->stream_index >= ctx_->nb_streams){ logIt("adjustPTS pkt stream index too much"); return; } if (pkt->pts == AV_NOPTS_VALUE && pkt->pts == AV_NOPTS_VALUE){ 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_); //内部时间戳 int64_t calc_duration = (int64_t)(AV_TIME_BASE / 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); return; } AVStream *in_stream,*out_stream; int out_idx = -1; for (auto i : in_streams_){ if (i->index == pkt->stream_index){ if (i->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ out_idx = v_idx_; in_stream = i; break; }else if (i->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){ in_stream = i; out_idx = a_idx_; break; } } } if (out_idx == -1) return; out_stream = ctx_->streams[out_idx]; pkt->stream_index = out_idx; // logIt("BEFORE in stream timebase %d:%d, out timebase %d:%d, // pts: %lld, dts: %lld, duration: %lld", // in_stream->time_base.num, in_stream->time_base.den, // out_stream->time_base.num, out_stream->time_base.den, // pkt->pts, pkt->dts, pkt->duration); //转换 PTS/DTS 时序 pkt->pts = av_rescale_q_rnd(pkt->pts,in_stream->time_base,out_stream->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX)); pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base); pkt->pos = -1; // logIt("AFTER stream %d, pts: %lld, dts: %lld, duration: %lld", // pkt->stream_index, pkt->pts, pkt->dts, pkt->duration); } bool FormatOut::writeFrame(AVPacket *pkt, const int64_t &frame_cnt, bool interleaved/* = true*/){ adjustPTS(pkt, frame_cnt); auto ret = writeFrame2(pkt, interleaved); if (!ret){ logIt("write to file failed, pkt.pts: %lld, dts: %lld, frame count: %d", pkt->pts, pkt->dts, frame_cnt); } return ret; } 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 < -22 || ret == 0){ return true; } return false; } 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; } }