#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") ,in_v_stream_(NULL) ,in_a_stream_(NULL) ,bsf_h264(NULL) ,bsf_hevc(NULL) {} FormatOut::~FormatOut() { clear(); if (bsf_h264) av_bsf_free(&bsf_h264); if (bsf_hevc) av_bsf_free(&bsf_hevc); } 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::addStream(AVStream *s){ AVStream *in_stream = s; 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_parameters_copy(out_stream->codecpar, in_stream->codecpar); if(ret<0) { logIt("Failed to copy context from input to output stream codec context\n"); return false; } out_stream->codecpar->codec_tag = 0; if(ctx_->oformat->flags & AVFMT_GLOBALHEADER) out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; return true; } bool FormatOut::copyCodecFromIn(AVStream *v, AVStream *a){ if (v){ v_idx_ = 0; in_v_stream_ = v; if (!addStream(v)){ logIt("format out add video stream error"); return false; }else{ logIt("copy video from instream"); } } if (a){ a_idx_ = 1; in_a_stream_ = a; if (!addStream(a)){ logIt("format out add audio stream error"); return false; }else{ logIt("copy audio from instream"); } } return true; } bool FormatOut::JustWriter(AVStream *v, AVStream *a, const char *filename){ if(ctx_){ clear(); } bool flag = open(NULL, format_name_.c_str()); flag = copyCodecFromIn(v, a) && 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); } if (v){ if (v->codecpar->codec_id == AV_CODEC_ID_H264){ char p[100] = {0}; char *sub = av_fourcc_make_string(p, v->codecpar->codec_tag); if (strcmp(sub, "avc1") == 0){ const AVBitStreamFilter *f = av_bsf_get_by_name("h264_mp4toannexb"); if (f){ if (av_bsf_alloc(f, &bsf_h264) >= 0){ if (avcodec_parameters_copy(bsf_h264->par_in, v->codecpar) >= 0){ if (av_bsf_init(bsf_h264) < 0) bsf_h264 = NULL; } } } } } } 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){ AVStream *in_stream,*out_stream; int out_idx = -1; std::vector in_streams{in_v_stream_, in_a_stream_}; for (auto i : in_streams){ if (i && (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; int64_t time_stamp = frame_cnt; if (out_idx == v_idx_){ pkt->pos = -1; AVRational time_base = ctx_->streams[out_idx]->time_base; AVRational time_base_q = { 1, AV_TIME_BASE }; int64_t calc_duration = (int64_t)(AV_TIME_BASE / fps_); //内部时间戳 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); }else if (out_idx == a_idx_){ pkt->duration = 1024; pkt->pts = pkt->dts = pkt->duration * time_stamp; } // 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); } bool FormatOut::writeFrame(AVPacket *pkt, const int64_t &frame_cnt, bool interleaved/* = true*/){ adjustPTS(pkt, frame_cnt); auto ret = writeFrameInternal(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; } static bool write_frame(AVFormatContext *ctx, 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::writeFrameInternal(AVPacket *pkt, bool interleaved){ // h264_mp4toanatax if (bsf_h264){ if (av_bsf_send_packet(bsf_h264, pkt) < 0){ logIt("bsf_h264 send packet failed"); return true; } int ret; while((ret = av_bsf_receive_packet(bsf_h264, pkt)) == 0){ if (!write_frame(ctx_, pkt, interleaved)) return false; } if (ret == AVERROR(EAGAIN)) return true; } return write_frame(ctx_, pkt, interleaved); } 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; } }