前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用FFmpeg实现软件转码监控视频

使用FFmpeg实现软件转码监控视频

作者头像
呱牛笔记
发布2023-05-02 15:08:07
3.2K0
发布2023-05-02 15:08:07
举报
文章被收录于专栏:呱牛笔记

实时监控视频的码率通常在5M以上码流,如果做手机端的实时预览,对带宽是很大的考验,所以很有必要先做降分辨率,然后降码率的处理。所有的处理在后台服务器进行,大致的业务流程如下:

海康监控摄像头输出的分辨率是:2560*1440 ,ffmpeg提供的方法能很好的完成这个流程,其实网上有很多例子,但都不全,去看ffmpeg源码提供的例子来实现是很好的办法,比方ffmpeg-4.1的例子代码在\ffmpeg-4.1\doc\examples,参考封装了一个类来做解码、缩放和编码的流程,代码如下图:

代码语言:javascript
复制
/* 
created:2019/04/02
*/
#ifndef RTP_RESAMPLE_TASK_H__
#define RTP_RESAMPLE_TASK_H__
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern "C" {
#include <libavcodec/avcodec.h>  
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavutil/base64.h>
 
#include <libavutil/imgutils.h>
#include <libavutil/parseutils.h>
#include <libswscale/swscale.h>
 
}
 
#include <stdio.h>
  
#include "Task.h"
#include "TimeoutTask.h"
 
#include "SVector.h"
 
 
typedef void(*EncodedCallbackFunc)(void*, char *, int);
 
typedef struct RtpData
{
    char *data;
    int length;    
}RtpData;
 
typedef struct DstVideoInfo
{
    int width;
    int height;
    int samplerate;
}DstVideoInfo;
 
typedef struct SourceVideoInfo{
    int payload_type;
    char *codec_type;
    int freq_hz;//90000
 
	char *sps;
	char *pps;
    long profile_level_id;
    char *media_header;
}SourceVideoInfo;
 
 
typedef struct
{
    struct AVCodec *codec;// Codec
    struct AVCodecContext *context;// Codec Context
    AVCodecParserContext *parser;
    struct AVFrame *frame;// Frame 
    AVPacket *pkt;
 
    int frame_count;
    int iWidth;
    int iHeight;
    int comsumedSize; 
    int skipped_frame;
 
    //for decodec
    void* accumulator;
    int accumulator_pos;
    int accumulator_size;
 
    char *szSourceDecodedSPS;
    int sps_len;
    char *szSourceDecodedPPS;
    int pps_len;
} X264_Decoder_Handler;
 
typedef struct
{
    struct AVCodec *codec;// Codec
    struct AVCodecContext *context;// Codec Context
    int frame_count;
    struct AVFrame *frame;// Frame  
    AVPacket *pkt;
 
    Bool16 force_idr;
    int quality; // [1-31]
 
    int iWidth;
    int iHeight;
    int comsumedSize;
    int got_picture;
 
    char *encodedBuffer;
    int encodedBufferSize;
    int encodedSize;
 
    char *rawBuffer;
    int rawBufferSize;
 
    struct SwsContext *sws_ctx;
 
    char *sps;
    int sps_len;
    char *pps;
    int pps_len;
 
    FILE *fTest;
} X264_Encoder_Handler;
 
class RTPResampleTask {
 
public:
    //output width, heigh, samplerate
    RTPResampleTask(const DstVideoInfo *dstVideoInfo, const SourceVideoInfo *sourceVideoInfo, EncodedCallbackFunc func, void *funcArgs);
    ~RTPResampleTask();
 
 
    int resample(const char *inputBuffer, int inputSize, char **outputBuffer, int *outputSize);
    char *getResampleSPS(int *sps_len){
        *sps_len = encoderHandler.sps_len;
        return encoderHandler.sps;
    };
    char *getResamplePPS(int *pps_len){
        *pps_len = encoderHandler.pps_len;
        return encoderHandler.pps;
    };
    enum{
        STATE_INIT = -1,
        STATE_RUNNING = 0,
        STATE_WAITING = 1,
        STATE_START_DECODE = 2,
        STATE_DONE = 3
    };
private:
    int initEncoder();
    int initDecoder();
 
    void deInitEncoder();
    void deInitDecoder();
 
    int decodePacket(const char *inputBuffer, int inputSize);
    int encodePacket();
private:
    UInt32          fState;
    UInt32          kIdleTime;
 
    EncodedCallbackFunc callback;
    SVector<RtpData *> reEncodedWaitList;
 
    SourceVideoInfo sourceVideoInfo;
    DstVideoInfo dstVideoInfo;
 
    X264_Decoder_Handler decoderHandler;
    X264_Encoder_Handler encoderHandler;
 
    void *funcArgs;
};
 
 
#endif//RTP_RESAMPLE_TASK_H__
代码语言:javascript
复制
/* 
created:2019/04/02
*/
 
#include "RTPResampleTask.h"
 
 
#define INBUF_SIZE 4096
#define H264_RTP_PAYLOAD_SIZE	1320
 
 
RTPResampleTask::RTPResampleTask(const DstVideoInfo *dstVideoInfo, const SourceVideoInfo *sourceVideoInfo, EncodedCallbackFunc func , void *funcArgs){
    uint8_t szDecodedSPS[128] = { 0 };
    this->decoderHandler.sps_len = av_base64_decode((uint8_t*)szDecodedSPS, (const char *)sourceVideoInfo->sps, sizeof(szDecodedSPS));//nSPSLen=24
    this->decoderHandler.szSourceDecodedSPS = (char *)malloc(this->decoderHandler.sps_len);
	if (this->decoderHandler.szSourceDecodedSPS != NULL){
		memcpy(this->decoderHandler.szSourceDecodedSPS, szDecodedSPS, this->decoderHandler.sps_len);
	}
 
    uint8_t szDecodedPPS[128] = { 0 };
    this->decoderHandler.pps_len = av_base64_decode(( uint8_t*)szDecodedPPS, (const char *)sourceVideoInfo->pps, sizeof(szDecodedPPS));//nPPSLen=4
    this->decoderHandler.szSourceDecodedPPS = (char *)malloc(this->decoderHandler.pps_len);
	if (this->decoderHandler.szSourceDecodedPPS != NULL){
		memcpy(this->decoderHandler.szSourceDecodedPPS, szDecodedPPS, this->decoderHandler.pps_len);
	}
 
	this->funcArgs = funcArgs;
 
    kIdleTime = 100;
    fState = STATE_INIT;
 
    initEncoder();
    initDecoder();
}
 
RTPResampleTask::~RTPResampleTask(){
 
    deInitEncoder();
    deInitDecoder();
}
 
int RTPResampleTask::initEncoder(){
    AVCodecID codec_id = AV_CODEC_ID_H264;
    
    if (codec_id == AV_CODEC_ID_H264){
 
    }
    encoderHandler.rawBuffer= NULL;
    encoderHandler.rawBufferSize = 0;
    encoderHandler.encodedBuffer= NULL;
    encoderHandler.encodedBufferSize = 0;
    encoderHandler.sws_ctx = NULL;
 
    encoderHandler.pkt = av_packet_alloc();
    if (!encoderHandler.pkt) { 
        printf("encoderHandler.pkt == NULL");
        return -1;
    }
    encoderHandler.codec = avcodec_find_encoder(codec_id); //AV_CODEC_ID_AAC;
    if(encoderHandler.codec == NULL ) {
        printf("encoderHandler.codec == NULL");
        return -1;
    } 
    //创建AVFormatContext结构体
    //分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行  
    encoderHandler.context = avcodec_alloc_context3(encoderHandler.codec); 
    if(encoderHandler.context == NULL ) {
        printf("encoderHandler.context == NULL");
        return -1;
    } 
    //设置AVCodecContext编码参数
    struct AVCodecContext *c      = encoderHandler.context;
    avcodec_get_context_defaults3(c, encoderHandler.codec);
    c->codec_id  = codec_id; 
    c->bit_rate = 500000;
    c->time_base.den = 25; //分母
    c->time_base.num = 1;  //分子
    /* resolution must be a multiple of two */
    c->width = 640;
    c->height = 480;
    /* frames per second */
    c->time_base = (AVRational){1, 25};
    c->framerate = (AVRational){25, 1};
    c->gop_size = 1;//
    c->max_b_frames = 0;
	//c->rtp_payload_size = H264_RTP_PAYLOAD_SIZE;
    c->pix_fmt = AV_PIX_FMT_YUV420P;
    c->codec_type = AVMEDIA_TYPE_VIDEO; 
    //c->flags|= CODEC_FLAG_GLOBAL_HEADER; 
    //AVOptions的参数
    av_opt_set(c->priv_data, "preset", "slow", 0);
    av_opt_set(c->priv_data, "preset", "ultrafast", 0);
    av_opt_set(c->priv_data, "tune","stillimage,fastdecode,zerolatency",0);
    av_opt_set(c->priv_data, "x264opts","crf=26:vbv-maxrate=728:vbv-bufsize=364:keyint=25",0); 
 
    encoderHandler.frame = av_frame_alloc();
    if(encoderHandler.frame == NULL )  {
        printf("encoderHandler.frame == NULL");
        return -1;
    } 
    encoderHandler.frame->format = c->pix_fmt;
    encoderHandler.frame->width  = c->width;
    encoderHandler.frame->height = c->height;
 
 
    avpicture_alloc((AVPicture *)encoderHandler.frame,AV_PIX_FMT_YUV420P, encoderHandler.frame->width, encoderHandler.frame->height);  //desW,desH分别为目标分辨率的宽度、高度
 
    /* open it */
    int ret = avcodec_open2(encoderHandler.context, encoderHandler.codec , NULL);
    if (ret < 0) {
        fprintf(stderr, "Could not open codec: %d\n", ret);
        return -1;
    }
 
    encoderHandler.fTest  = fopen("/tmp/test_out.h264", "wb");
    if (!encoderHandler.fTest) {
        fprintf(stderr, "Could not open /tmp/test_out.h264 \n");
        return -1;
    }
 
    encoderHandler.sps = NULL;
    encoderHandler.sps_len = 0;
    encoderHandler.pps = NULL;
    encoderHandler.pps_len = 0;
 
	return 0;
}
 
void RTPResampleTask::deInitEncoder(){ 
    if(encoderHandler.context){ 
        avcodec_free_context(&encoderHandler.context);
        encoderHandler.context = NULL;
    }
    if(encoderHandler.frame){
        av_frame_free(&encoderHandler.frame);
        encoderHandler.frame = NULL;
    }
    if (encoderHandler.pkt){
        av_packet_free(&encoderHandler.pkt);
        encoderHandler.pkt = NULL;
    }
    if (encoderHandler.sws_ctx){
        sws_freeContext(encoderHandler.sws_ctx);
        encoderHandler.sws_ctx = NULL;
    }
    if (encoderHandler.fTest){
        fclose(encoderHandler.fTest);
    }
    if (encoderHandler.sps != NULL){
        free(encoderHandler.sps);
    }
    if (encoderHandler.pps != NULL){
        free(encoderHandler.pps);
    }
}
 
 
int RTPResampleTask::initDecoder(){
    AVCodecID codec_id = AV_CODEC_ID_H264;
    
    if (codec_id == AV_CODEC_ID_H264){
 
    }
    decoderHandler.pkt = av_packet_alloc();
    if (!decoderHandler.pkt) { 
        printf("decoderHandler.pkt == NULL");
        return -1;
    }
 
    decoderHandler.codec = avcodec_find_decoder(codec_id); //AV_CODEC_ID_AAC;
    if(decoderHandler.codec == NULL )
    {
        printf("decoderHandler.codec == NULL");
        return -1;
    } 
    decoderHandler.parser = av_parser_init(decoderHandler.codec->id);
    if (!decoderHandler.parser) { 
        printf("decoderHandler.parser == NULL");
        return -1;
    }
    //创建AVFormatContext结构体
    //分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行  
    decoderHandler.context = avcodec_alloc_context3(decoderHandler.codec); 
    if (!decoderHandler.context) { 
        printf("decoderHandler.context == NULL");
        return -1;
    }
 
    int sps_pps_len = decoderHandler.sps_len + decoderHandler.pps_len + 6;
    unsigned char *szSPSPPS = (unsigned char *)av_mallocz(sps_pps_len + 1);
    char spsHeader[4] = {0x00, 0x00,0x00,  0x01};
    char ppsHeader[3] = {0x00, 0x00, 0x01};
    memcpy(szSPSPPS, spsHeader, sizeof(spsHeader));
    memcpy(szSPSPPS + sizeof(spsHeader), decoderHandler.szSourceDecodedSPS, decoderHandler.sps_len);
    memcpy((void *)(szSPSPPS + sizeof(spsHeader)  + decoderHandler.sps_len), ppsHeader, sizeof(ppsHeader));
    memcpy((void *)(szSPSPPS + sizeof(spsHeader)  + sizeof(ppsHeader) + decoderHandler.sps_len), decoderHandler.szSourceDecodedPPS, decoderHandler.pps_len);
 
    decoderHandler.context->extradata_size = sps_pps_len;
    decoderHandler.context->extradata = (uint8_t*)av_mallocz(decoderHandler.context->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
    memcpy(decoderHandler.context->extradata, szSPSPPS, sps_pps_len);
 
    decoderHandler.context->time_base.num = 1;
    decoderHandler.context->time_base.den = 25;
    if (avcodec_open2(decoderHandler.context, decoderHandler.codec, NULL) < 0) {
           
        printf("avcodec_open2 return failed");
        return -1;
    }
    decoderHandler.frame = av_frame_alloc();
    if (!decoderHandler.frame) { 
        printf("decoderHandler.frame is null.");
        return -1;
    }
        
    printf("initDecoder, width:%d, height:%d", decoderHandler.context->width, decoderHandler.context->height);
    return 0;
}
 
void RTPResampleTask::deInitDecoder(){
    if(decoderHandler.frame){
        av_frame_free(&decoderHandler.frame);
        decoderHandler.frame = NULL;
    }    
    if(decoderHandler.parser){
        av_parser_close(decoderHandler.parser);
        decoderHandler.parser = NULL;
    }
    if (decoderHandler.pkt){
        av_packet_free(&decoderHandler.pkt);
    }
    if (decoderHandler.context->extradata != NULL){
        av_free(decoderHandler.context->extradata);
    }
    if(decoderHandler.context){
        avcodec_free_context(&decoderHandler.context); 
        decoderHandler.context = NULL;
    }
 
}  
 
 
int RTPResampleTask::resample(const char *inputBuffer, int inputSize, char **outputBuffer, int *outputSize){
    *outputBuffer = NULL;
    *outputSize = 0;
    int ret = decodePacket((const char *)inputBuffer, inputSize);
    //printf("video resample decodePacket ret:%d outputSize:%d\n", ret, encoderHandler.rawBufferSize);
    if (ret == 0){
        char *encodeOutputBuffer = NULL;
        int encodeOutputSize = 0;
        int src_linesize[4], dst_linesize[4];
 
        //scale
        if (encoderHandler.sws_ctx == NULL){
            encoderHandler.sws_ctx = sws_getContext(decoderHandler.context->width, decoderHandler.context->height, decoderHandler.context->pix_fmt,
                             encoderHandler.context->width, encoderHandler.context->height, AV_PIX_FMT_YUV420P,
                             SWS_BILINEAR, NULL, NULL, NULL);
        }
        sws_scale(encoderHandler.sws_ctx,  decoderHandler.frame->data,
                  decoderHandler.frame->linesize, 0, decoderHandler.context->height, encoderHandler.frame->data, encoderHandler.frame->linesize);
 
        ret = encodePacket();
        if (ret != 0){
            printf("video resample ret:%d err\n", ret);
            return -1;
        } 
        fwrite(encoderHandler.encodedBuffer, 1, encoderHandler.encodedSize, encoderHandler.fTest);
        *outputBuffer = encoderHandler.encodedBuffer;
        *outputSize = encoderHandler.encodedSize;
        int typeIndex = 3;//default 00 00 01 
        if (encoderHandler.encodedBuffer[3] == 0x01){
            typeIndex = 4;
        }
        
        if (encoderHandler.encodedBuffer[typeIndex] == 0x67  && encoderHandler.sps == NULL){
            printf("video resample has sps:%d  \n", encoderHandler.encodedSize);  
            encoderHandler.sps_len = 0;
            int i = 0;
            unsigned short find_sps_flag = 0;
            while(i++ < encoderHandler.encodedSize){
                if ((encoderHandler.encodedBuffer[typeIndex] == 0x00 &&
                encoderHandler.encodedBuffer[typeIndex + 1] == 0x00 && 
                encoderHandler.encodedBuffer[typeIndex + 2] == 0x01 && 
                encoderHandler.encodedBuffer[typeIndex + 3] == 0x68)
                ){
                    find_sps_flag = 1;
                    break;
                }
                typeIndex++;
            }      
            if (!find_sps_flag){
                return 0;    
            }
            find_sps_flag = 0;
 
            encoderHandler.sps_len = typeIndex;
            printf("video resample encoderHandler.sps_len:%d  \n", encoderHandler.sps_len);  
            
            encoderHandler.sps = (char *)malloc(encoderHandler.sps_len);
            if (encoderHandler.sps != NULL){
                memcpy((void *)encoderHandler.sps, (void *)encoderHandler.encodedBuffer, encoderHandler.sps_len);
            }
            encoderHandler.pps_len = 0;
            typeIndex = encoderHandler.sps_len + 4;
            while(i++ < encoderHandler.encodedSize){
                if (
                    (encoderHandler.encodedBuffer[typeIndex] == 0x00 &&
                    encoderHandler.encodedBuffer[typeIndex +1] == 0x00 &&  
                    encoderHandler.encodedBuffer[typeIndex +2] == 0x01)
                ){
                    find_sps_flag = 1;
                    break;
                }
                typeIndex++;
                encoderHandler.pps_len++;
            }     
            if (!find_sps_flag){
                return 0;    
            }
            encoderHandler.pps_len += 4;
 
            printf("video resample encoderHandler.pps_len:%d  \n", encoderHandler.pps_len);   
            encoderHandler.pps = (char *)malloc(encoderHandler.pps_len);
            if (encoderHandler.pps != NULL){
                memcpy((void *)encoderHandler.pps, &encoderHandler.encodedBuffer[encoderHandler.sps_len], encoderHandler.pps_len);
            } 
        }
        return 0;  
    } 
        
    return -1; 
}
 
int RTPResampleTask::decodePacket(const char *inputBuffer, int inputSize){
 
    int ret;  
    uint8_t *data = ( uint8_t *)inputBuffer;
    int data_size = inputSize;
    //while (data_size > 0) 
    { 
        encoderHandler.rawBufferSize =   0;
        av_init_packet(decoderHandler.pkt);
        decoderHandler.pkt->dts  = AV_NOPTS_VALUE;
        decoderHandler.pkt->size = (int)inputSize;
        decoderHandler.pkt->data = (uint8_t *)inputBuffer;  
        if (decoderHandler.pkt->size){
		    ret = avcodec_send_packet(decoderHandler.context, decoderHandler.pkt);
		    if (ret < 0) {
		        fprintf(stderr, "decodePacket Error sending a packet for decoding\n");
		        return -1;
		    }
 
	        ret = avcodec_receive_frame(decoderHandler.context, decoderHandler.frame);
	        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){  
	            return -1;
	        }
	        else if (ret < 0) {
	            fprintf(stderr, "decodePacket Error during decoding :%d\n", ret);
	            return -1;
	        } 
	    	int xsize = avpicture_get_size(decoderHandler.context->pix_fmt, decoderHandler.context->width, decoderHandler.context->height);	
            //fprintf(stderr, "decodePacket end decoding :%d, width:%d, height:%d\n", xsize, decoderHandler.context->width, decoderHandler.context->height);
            encoderHandler.rawBufferSize = xsize;
            //
        }else{ 
        	return -1;
        }
    }
 
	return 0;
}
 
int RTPResampleTask::encodePacket(){
    #if 0
    int size = avpicture_fill((AVPicture*)encoderHandler.frame, (uint8_t*)inputBuffer, AV_PIX_FMT_YUV420P, encoderHandler.context->width, encoderHandler.context->height);
    if (size != inputSize){
        /* guard */
        printf("RTPResampleTask::encodePacket invalid size: %u<>%u", size, inputSize);
        return 0;
    }
    #endif//encoderHandler.frame
    int ret = avcodec_send_frame(encoderHandler.context, encoderHandler.frame);
    if (ret < 0) {
        printf("RTPResampleTask::encodePacket error sending a frame for encoding, ret:%d\n", ret);
        return ret;
    }
 
    encoderHandler.encodedSize = 0;
 
    while (ret >= 0) 
    {
        ret = avcodec_receive_packet(encoderHandler.context, encoderHandler.pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){ 
            if (encoderHandler.encodedSize > 0){
                return 0;
            }
            return -1;
        } else if (ret < 0) {
            printf("RTPResampleTask::encodePacket error during encoding\n");
            return ret;
        }
        if (encoderHandler.encodedBuffer == NULL){ 
            encoderHandler.encodedBuffer = (char *)malloc((encoderHandler.context->width*encoderHandler.context->height * 3)/2);   
            encoderHandler.encodedBufferSize = (encoderHandler.context->width*encoderHandler.context->height * 3)/2;
        }
 
        if (encoderHandler.encodedBufferSize < (encoderHandler.pkt->size + encoderHandler.encodedSize)){ 
            encoderHandler.encodedBuffer = (char *)realloc(encoderHandler.encodedBuffer, encoderHandler.pkt->size + encoderHandler.encodedSize); 
            encoderHandler.encodedBufferSize += encoderHandler.pkt->size + encoderHandler.encodedSize;
        }
    	if (encoderHandler.encodedBuffer){
    		memcpy((void *)(encoderHandler.encodedBuffer + encoderHandler.encodedSize), encoderHandler.pkt->data, encoderHandler.pkt->size);
    	}
        encoderHandler.encodedSize += encoderHandler.pkt->size;
        //printf("RTPResampleTask::encodePacket success ret:%d\n", encoderHandler.pkt->size); 
        //if (callback != NULL){
            //callback(funcArgs, (char*)encoderHandler.pkt->data, encoderHandler.pkt->size);
        //}
        av_packet_unref(encoderHandler.pkt);
    }
    //printf("RTPResampleTask::encodePacket success ret:%d\n", encoderHandler.encodedBufferSize); 
	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/03/23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档