| | |
| | | |
| | | #include <stdexcept> |
| | | #include <unistd.h> |
| | | #include <stdlib.h> |
| | | #include <time.h> |
| | | |
| | | extern "C"{ |
| | | #include <libavformat/avformat.h> |
| | |
| | | #include <libavutil/opt.h> |
| | | #include <libavutil/avassert.h> |
| | | #include <libavutil/imgutils.h> |
| | | #include <libswscale/swscale.h> |
| | | #include <libswscale/swscale.h> |
| | | } |
| | | |
| | | #include "../log/log.hpp" |
| | |
| | | #include "../property/VideoProp.hpp" |
| | | |
| | | #include "../../common/gpu/info.h" |
| | | |
| | | using namespace logif; |
| | | |
| | | namespace ffwrapper{ |
| | |
| | | ,dec_ctx_(NULL) |
| | | ,vs_idx_(-1) |
| | | ,as_idx_(-1) |
| | | ,hw_accl_(hw) |
| | | ,prop_(NULL) |
| | | ,io_ctx_(NULL) |
| | | ,read_io_buff_(NULL) |
| | | ,read_io_buff_size_(32768) |
| | | ,handle_gb28181(NULL) |
| | | ,fps_(25.0) |
| | | {} |
| | | |
| | | FormatIn::FormatIn(const VideoProp &prop, bool hw/*=true*/) |
| | | :FormatIn(hw) |
| | | { |
| | | prop_ = new VideoProp; |
| | | *prop_ = prop; |
| | | } |
| | | |
| | | FormatIn::~FormatIn() |
| | | { |
| | | if(io_ctx_){ |
| | | if(read_io_buff_){ |
| | | // av_free(read_io_buff_); |
| | | read_io_buff_ = NULL; |
| | | } |
| | | avio_context_free(&io_ctx_); |
| | | io_ctx_ = NULL; |
| | | logIt("free format in"); |
| | | if (prop_) delete prop_; |
| | | |
| | | if(dec_ctx_){ |
| | | avcodec_close(dec_ctx_); |
| | | avcodec_free_context(&dec_ctx_); |
| | | dec_ctx_ = NULL; |
| | | } |
| | | |
| | | if(ctx_){ |
| | | if (!ctx_->oformat){ |
| | | avformat_free_context(ctx_); |
| | | }else{ |
| | | avformat_close_input(&ctx_); |
| | | } |
| | | avformat_close_input(&ctx_); |
| | | ctx_ = NULL; |
| | | if(dec_ctx_){ |
| | | avcodec_close(dec_ctx_); |
| | | dec_ctx_ = NULL; |
| | | } |
| | | |
| | | } |
| | | if (handle_gb28181){ |
| | | delete handle_gb28181; |
| | | |
| | | if(io_ctx_){ |
| | | av_freep(&io_ctx_->buffer); |
| | | av_freep(&io_ctx_); |
| | | io_ctx_ = NULL; |
| | | } |
| | | } |
| | | |
| | | //////////////////////////////////////////////////////////////////////// |
| | | int FormatIn::openWithCustomIO(void *opaque, read_packet fn, AVDictionary **options/*=NULL*/){ |
| | | ctx_ = avformat_alloc_context(); |
| | | if(!ctx_){ |
| | | logIt("open with custom io create format error\n"); |
| | | return -1; |
| | | } |
| | | read_io_buff_ = (uint8_t*)av_malloc(read_io_buff_size_); |
| | | if(!read_io_buff_){ |
| | | logIt("open with custom io alloc read io buff error\n"); |
| | |
| | | logIt("open with custom io create custom avio error\n"); |
| | | return -1; |
| | | } |
| | | ctx_->pb = io_ctx_; |
| | | |
| | | auto err = av_probe_input_buffer(ctx_->pb, &ctx_->iformat, NULL, NULL, 0, 0); |
| | | if(err != 0){ |
| | | logIt("open with custom io prob input buffer error:%d err: %s\n", err, getAVErrorDesc(err).c_str()); |
| | | ctx_ = avformat_alloc_context(); |
| | | if(!ctx_){ |
| | | logIt("open with custom io create format error\n"); |
| | | return -1; |
| | | } |
| | | |
| | | ctx_->pb = io_ctx_; |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | int FormatIn::openGb28181(const char *filename, AVDictionary **options){ |
| | | |
| | | std::string fn = filename; |
| | | //GB28181API gb28181(fn); |
| | | if (handle_gb28181){ |
| | | delete handle_gb28181; |
| | | } |
| | | handle_gb28181 = new GB28181API; |
| | | if(handle_gb28181->addCamera(fn) == -1){ |
| | | logIt("do addCamera Error\n"); |
| | | return -1; |
| | | } |
| | | |
| | | int ret = openWithCustomIO(handle_gb28181, handle_gb28181->readData, options); |
| | | if(ret < 0){ |
| | | logIt("do openWithCustomIO failed:%d",ret); |
| | | }else{ |
| | | ret = avformat_open_input(&ctx_, "", NULL, options); |
| | | } |
| | | |
| | | // if(ret < 0){ |
| | | // logIt("open %s failed:%s",filename, |
| | | // getAVErrorDesc(ret).c_str()); |
| | | // } |
| | | |
| | | return ret; |
| | | } |
| | | |
| | | ///////////////////////////////////////////////////////////////////////// |
| | | int FormatIn::open(const char *filename, AVDictionary **options){ |
| | | |
| | | const int ret = avformat_open_input(&ctx_, filename, NULL, options); |
| | | // if(ret < 0){ |
| | | // logIt("open %s failed:%s",filename, |
| | | // getAVErrorDesc(ret).c_str()); |
| | | // } |
| | | int ret = avformat_open_input(&ctx_, filename, NULL, options); |
| | | |
| | | return ret; |
| | | } |
| | | |
| | | bool FormatIn::findStreamInfo(AVDictionary **options){ |
| | | |
| | | const int ret = avformat_find_stream_info(ctx_, options); |
| | | ret = avformat_find_stream_info(ctx_, options); |
| | | if(ret < 0){ |
| | | logIt("find %s stream info failed:%s", |
| | | ctx_->filename,getAVErrorDesc(ret).c_str()); |
| | | |
| | | return false; |
| | | ctx_->filename,getAVErrorDesc(ret).c_str()); |
| | | return ret; |
| | | } |
| | | |
| | | // logIt("there are %d stream", ctx_->nb_streams); |
| | | |
| | | for (int i = 0; i < ctx_->nb_streams; ++i) |
| | | { |
| | |
| | | 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); |
| | | } |
| | | logIt("in stream video fps %f, time_base: %d : %d", fps_, in->time_base.num, in->time_base.den); |
| | | } |
| | | logIt("in stream video fps %f, time_base: %d:%d, size: %dx%d", fps_, in->time_base.num, in->time_base.den, in->codecpar->width, in->codecpar->height); |
| | | } |
| | | if (type == AVMEDIA_TYPE_AUDIO){ |
| | | auto in = ctx_->streams[i]; |
| | | logIt("in stream audio %d time_base: %d : %d", in->codecpar->codec_id, in->time_base.num, in->time_base.den); |
| | | logIt("in stream audio %d time_base: %d:%d", in->codecpar->codec_id, in->time_base.num, in->time_base.den); |
| | | if (in->codecpar->codec_id == AV_CODEC_ID_AAC) |
| | | as_idx_ = i; |
| | | else |
| | |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | return 0; |
| | | } |
| | | |
| | | const bool FormatIn::IsHEVC()const{ |
| | | return ctx_->streams[vs_idx_]->codecpar->codec_id == AV_CODEC_ID_HEVC; |
| | | } |
| | | |
| | | const bool FormatIn::IsAVC1()const{ |
| | | if (IsHEVC()) return false; |
| | | |
| | | char p[100] = {0}; |
| | | char *sub = av_fourcc_make_string(p, ctx_->streams[vs_idx_]->codecpar->codec_tag); |
| | | const int ret = strcmp(sub, "avc1"); |
| | | if (ret == 0) return true; |
| | | return false; |
| | | } |
| | | |
| | | bool FormatIn::openCodec(AVDictionary **options){ |
| | | if (dec_ctx_) return true; |
| | | if (vs_idx_ == -1) return false; |
| | | |
| | | AVStream *s = ctx_->streams[vs_idx_]; |
| | | |
| | | AVCodecParameters *codecpar = s->codecpar; |
| | | AVCodec *dec = NULL; |
| | | |
| | | bool flag = false; |
| | | AVDictionary *avdic = NULL; |
| | | |
| | | int idle_gpu = -1; |
| | | AVCodecID codec_id = codecpar->codec_id; |
| | | |
| | | for (int i = 0; i < 2; ++i) |
| | | { |
| | | if(hw_accl_){ |
| | | idle_gpu = gpu::getGPU(100); |
| | | if(idle_gpu < 0){ |
| | | logIt("NO GPU RESOURCE TO DECODE"); |
| | | hw_accl_ = false; |
| | | continue; |
| | | } |
| | | if(codecpar->codec_id == AV_CODEC_ID_H264){ |
| | | dec = avcodec_find_decoder_by_name("h264_cuvid"); |
| | | }else if(codecpar->codec_id == AV_CODEC_ID_H265){ |
| | | dec = avcodec_find_decoder_by_name("hevc_cuvid"); |
| | | } |
| | | if(!dec){ |
| | | hw_accl_ = false; |
| | | continue; |
| | | }else{ |
| | | av_dict_set(&avdic, "gpu", std::to_string(idle_gpu).c_str(), 0); |
| | | } |
| | | }else{ |
| | | dec = avcodec_find_decoder(codecpar->codec_id); |
| | | AVCodec *dec = avcodec_find_decoder(codec_id); |
| | | |
| | | if(dec){ |
| | | flag = allocCodec(dec, s, options); |
| | | if(*options){ |
| | | av_dict_free(options); |
| | | } |
| | | if(dec){ |
| | | if(avdic){ |
| | | options = &avdic; |
| | | logIt("DECODE USE GPU %d", idle_gpu); |
| | | } |
| | | flag = allocCodec(dec, s, options); |
| | | if(avdic){ |
| | | av_dict_free(&avdic); |
| | | } |
| | | if(flag){ |
| | | logIt("use decoder %s\n", dec->name); |
| | | break; |
| | | }else{ |
| | | av_free(dec_ctx_); |
| | | dec_ctx_ = NULL; |
| | | hw_accl_ = false; |
| | | } |
| | | if(!flag){ |
| | | av_free(dec_ctx_); |
| | | dec_ctx_ = NULL; |
| | | } |
| | | |
| | | logIt("use decoder %s\n", dec->name); |
| | | } |
| | | |
| | | |
| | | return flag; |
| | | } |
| | | |
| | | bool FormatIn::allocCodec(AVCodec *dec, AVStream *s, AVDictionary **options){ |
| | | |
| | | AVCodecParameters *codecpar = s->codecpar; |
| | | AVCodecParameters *codecpar = NULL; |
| | | if(s) codecpar = s->codecpar; |
| | | |
| | | dec_ctx_ = avcodec_alloc_context3(dec); |
| | | if (!dec_ctx_){ |
| | | logIt("avcodec_alloc_context3 error"); |
| | | return false; |
| | | } |
| | | int ret = avcodec_parameters_to_context(dec_ctx_, codecpar); |
| | | if(ret < 0){ |
| | | logIt("avcodec_parameters_to_context error : %s", getAVErrorDesc(ret).c_str()); |
| | | return false; |
| | | int ret = 0; |
| | | if(s && codecpar) { |
| | | ret = avcodec_parameters_to_context(dec_ctx_, codecpar); |
| | | if(ret < 0) { |
| | | logIt("avcodec_parameters_to_context error : %s", getAVErrorDesc(ret).c_str()); |
| | | return false; |
| | | } |
| | | av_codec_set_pkt_timebase(dec_ctx_, s->time_base); |
| | | dec_ctx_->framerate = av_guess_frame_rate(ctx_, s, NULL); |
| | | } |
| | | av_codec_set_pkt_timebase(dec_ctx_, s->time_base); |
| | | |
| | | dec_ctx_->framerate = av_guess_frame_rate(ctx_, s, NULL); |
| | | |
| | | dec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; |
| | | ret = avcodec_open2(dec_ctx_,dec, options); |
| | |
| | | } |
| | | |
| | | AVStream *FormatIn::getStream(int type/*=-1*/){ |
| | | if (vs_idx_ < 0 || !ctx_ || ctx_->nb_streams == 0 || !ctx_->streams) return NULL; |
| | | |
| | | if (type == -1){ |
| | | return ctx_->streams[vs_idx_]; |
| | | } |
| | |
| | | } |
| | | |
| | | int FormatIn::readPacket(AVPacket *pkt_out){ |
| | | |
| | | auto flag = av_read_frame(ctx_, pkt_out); |
| | | |
| | | if (flag < 0) |
| | | logIt("======>> av_read_frame error %s", getAVErrorDesc(flag).c_str()); |
| | | return flag; |
| | | } |
| | | |
| | | int FormatIn::decode(AVFrame* frame, AVPacket *pkt){ |
| | | |
| | | AVStream *in = getStream(); |
| | | |
| | | av_packet_rescale_ts(pkt, in->time_base, in->codec->time_base); |
| | | if (in){ |
| | | av_packet_rescale_ts(pkt, in->time_base, in->codec->time_base); |
| | | } |
| | | int ret = avcodec_send_packet(dec_ctx_, pkt); |
| | | if(ret < 0){ |
| | | logIt("avcodec_send_packet error : %s", getAVErrorDesc(ret).c_str()); |
| | | logIt("pkt size %d avcodec_send_packet error : %s", pkt->size, getAVErrorDesc(ret).c_str()); |
| | | return -1; |
| | | } |
| | | while (ret >= 0) { |
| | |
| | | } |
| | | return -2; |
| | | } |
| | | |
| | | ////////////////////////////////////////////////////////////////////// |
| | | constexpr int bs = 8192; |
| | | FormatInGB::FormatInGB() |
| | | :FormatIn() |
| | | ,gb28181_(NULL) |
| | | ,parser_ctx_(NULL) |
| | | ,buffer_(NULL) |
| | | ,buffer_size_(bs) |
| | | { |
| | | buffer_ = (unsigned char*)malloc(buffer_size_); |
| | | } |
| | | |
| | | FormatInGB::FormatInGB(const VideoProp &prop) |
| | | :FormatInGB() |
| | | { |
| | | prop_ = new VideoProp; |
| | | *prop_ = prop; |
| | | } |
| | | |
| | | FormatInGB::~FormatInGB(){ |
| | | if (parser_ctx_){ |
| | | av_parser_close(parser_ctx_); |
| | | } |
| | | if (gb28181_)delete gb28181_; |
| | | if (buffer_) free(buffer_); |
| | | |
| | | for(auto &pkt : q_pkt_){ |
| | | av_packet_free(&pkt); |
| | | } |
| | | q_pkt_.clear(); |
| | | } |
| | | |
| | | static enum AVCodecID codecMap(const int c){ |
| | | switch (c) { |
| | | case E_VIDEO_STREAM_H264:// = 0, |
| | | return AV_CODEC_ID_H264; |
| | | case E_VIDEO_STREAM_MPEG2:// = 1, // MPEG4 |
| | | // return AV_CODEC_ID_MPEG2VIDEO; |
| | | case E_VIDEO_STREAM_MPEG4:// = 2, // MPEG4 |
| | | return AV_CODEC_ID_MPEG4; |
| | | case E_VIDEO_STREAM_SVAC:// = 3, // SVAC |
| | | return AV_CODEC_ID_NONE; |
| | | case E_VIDEO_STREAM_3GP:// = 4, // 3GP |
| | | return AV_CODEC_ID_NONE; // audio |
| | | case E_VIDEO_STREAM_H265:// = 5, //H265 |
| | | return AV_CODEC_ID_HEVC; |
| | | default: |
| | | break; |
| | | } |
| | | return AV_CODEC_ID_NONE; |
| | | } |
| | | |
| | | int FormatInGB::open(const char *filename, AVDictionary **options){ |
| | | if (gb28181_){ |
| | | delete gb28181_; |
| | | } |
| | | |
| | | gb28181_ = new GB28181API; |
| | | std::string fn = filename; |
| | | |
| | | if(gb28181_->addCamera(fn) < 0){ |
| | | delete gb28181_; |
| | | gb28181_ = NULL; |
| | | logIt("do addCamera Error\n"); |
| | | return -1; |
| | | } |
| | | for(int i = 0; i < 6; i++){ |
| | | if (gb28181_->getDataType() >= 0){ |
| | | AVCodecID id = codecMap(gb28181_->getDataType()); |
| | | logIt("======>>codec name %s\n", avcodec_get_name(id)); |
| | | parser_ctx_ = av_parser_init(id); |
| | | if (parser_ctx_) parser_ctx_->flags |= PARSER_FLAG_USE_CODEC_TS; |
| | | AVCodec* dec = avcodec_find_decoder(id); |
| | | allocCodec(dec, NULL, NULL); |
| | | break; |
| | | } |
| | | usleep(1000000); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | int FormatInGB::readPacket(AVPacket *pkt_out){ |
| | | if (!q_pkt_.empty()){ |
| | | auto pkt = q_pkt_.front(); |
| | | q_pkt_.pop_front(); |
| | | av_packet_ref(pkt_out, pkt); |
| | | av_packet_free(&pkt); |
| | | return 0; |
| | | } |
| | | |
| | | if (gb28181_->getDataType() < 0){ |
| | | logIt("======>> readPacket can't recv gb28181 stream"); |
| | | return 1; |
| | | } |
| | | if (!parser_ctx_){ |
| | | AVCodecID id = codecMap(gb28181_->getDataType()); |
| | | parser_ctx_ = av_parser_init(id); |
| | | AVCodec* dec = avcodec_find_decoder(id); |
| | | allocCodec(dec, NULL, NULL); |
| | | } |
| | | if (parser_ctx_ && dec_ctx_){ |
| | | |
| | | int try_run = 0; |
| | | AVPacket* pkt = av_packet_alloc(); |
| | | bool got_pkt = false; |
| | | |
| | | while (true) { |
| | | int data_size = gb28181_->readData(gb28181_, buffer_, buffer_size_); |
| | | // printf("======>> data_size %d pos %d\n", data_size, buffer_pos_); |
| | | if (data_size == 0){ |
| | | try_run ++; |
| | | if (try_run > 12){ |
| | | av_packet_free(&pkt); |
| | | logIt("gb28181_ readData %d failed, return -1", try_run); |
| | | return -1; |
| | | } |
| | | continue; |
| | | } |
| | | try_run = 0; |
| | | unsigned char* data = buffer_; |
| | | while (data_size > 0) { |
| | | int ret = av_parser_parse2(parser_ctx_, dec_ctx_, |
| | | &pkt->data, &pkt->size, data, data_size, |
| | | AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); |
| | | |
| | | // logIt("======>> av_parser_parse2 ret %d pkt size %d", ret, pkt->size); |
| | | if (ret < 0) { |
| | | av_packet_free(&pkt); |
| | | logIt("======>> av_parser_parse2 error %d %s", ret, getAVErrorDesc(ret).c_str()); |
| | | return ret; |
| | | } |
| | | |
| | | data += ret; |
| | | data_size -= ret; |
| | | |
| | | if (pkt->size){ |
| | | if(fps_ == 0 && dec_ctx_->framerate.den > 0 && dec_ctx_->framerate.num > 0){ |
| | | fps_ = dec_ctx_->framerate.num/dec_ctx_->framerate.den; |
| | | if (fps_ == 0) fps_ = 24; |
| | | } |
| | | if (parser_ctx_->key_frame == 1){ |
| | | pkt->flags |= AV_PKT_FLAG_KEY; |
| | | } |
| | | got_pkt = true; |
| | | AVPacket* tmpkt = av_packet_alloc(); |
| | | av_packet_ref(tmpkt, pkt); |
| | | q_pkt_.push_back(tmpkt); |
| | | } |
| | | } |
| | | if (got_pkt) { |
| | | av_packet_free(&pkt); |
| | | auto tmpkt = q_pkt_.front(); |
| | | q_pkt_.pop_front(); |
| | | av_packet_ref(pkt_out, tmpkt); |
| | | av_packet_free(&tmpkt); |
| | | return 0; |
| | | } |
| | | } |
| | | } |
| | | return -1; |
| | | } |
| | | |
| | | const bool FormatInGB::IsHEVC()const{ |
| | | if (!gb28181_) return false; |
| | | return codecMap(gb28181_->getDataType()) == AV_CODEC_ID_HEVC; |
| | | } |
| | | |
| | | const bool FormatInGB::IsAVC1()const{ |
| | | return false; |
| | | } |
| | | |
| | | bool FormatInGB::isVideoPkt(AVPacket *pkt) { |
| | | return true; |
| | | } |
| | | bool FormatInGB::isAudioPkt(AVPacket *pkt) { |
| | | return false; |
| | | } |
| | | |
| | | } |