#include "FormatOut.hpp"
|
|
#include <stdexcept>
|
#include <vector>
|
|
#include <unistd.h>
|
#include <sys/time.h>
|
|
extern "C"{
|
#include <libavformat/avformat.h>
|
#include <libavcodec/avcodec.h>
|
#include <libavutil/opt.h>
|
#include <libavutil/intreadwrite.h>
|
}
|
|
#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);
|
|
// h264 mp4toannexb
|
if (v && v->codecpar->codec_id == AV_CODEC_ID_H264) {
|
if (!bsf_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;
|
}
|
}
|
}
|
}
|
}
|
}
|
// h264 mp4toannexb
|
|
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){
|
AVStream *in_stream,*out_stream;
|
int out_idx = -1;
|
std::vector<AVStream*> 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){
|
|
AVBSFContext *bsf = NULL;
|
|
if (in_v_stream_ && in_v_stream_->codecpar->codec_id == AV_CODEC_ID_HEVC) {
|
if (pkt->size >= 5 &&
|
AV_RB32(pkt->data) != 0x0000001 &&
|
AV_RB24(pkt->data) != 0x000001 &&
|
!bsf_hevc){
|
|
const AVBitStreamFilter *f = av_bsf_get_by_name("hevc_mp4toannexb");
|
if (f){
|
if (av_bsf_alloc(f, &bsf_hevc) >= 0){
|
if (avcodec_parameters_copy(bsf_hevc->par_in, in_v_stream_->codecpar) >= 0){
|
if (av_bsf_init(bsf_hevc) < 0) bsf_hevc = NULL;
|
}
|
}
|
}
|
}
|
|
bsf = bsf_hevc;
|
logIt("use hevc AVBitStreamFilter");
|
}else {
|
bsf = bsf_h264;
|
logIt("use h264 AVBitStreamFilter");
|
}
|
|
if (bsf){
|
if (av_bsf_send_packet(bsf, pkt) < 0){
|
logIt("bsf send packet failed");
|
return true;
|
}
|
if (av_bsf_receive_packet(bsf, pkt) < 0){
|
logIt("bsf recv packet failed");
|
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;
|
}
|
|
}
|