实时监控视频的码率通常在5M以上码流,如果做手机端的实时预览,对带宽是很大的考验,所以很有必要先做降分辨率,然后降码率的处理。所有的处理在后台服务器进行,大致的业务流程如下:
海康监控摄像头输出的分辨率是:2560*1440 ,ffmpeg提供的方法能很好的完成这个流程,其实网上有很多例子,但都不全,去看ffmpeg源码提供的例子来实现是很好的办法,比方ffmpeg-4.1的例子代码在\ffmpeg-4.1\doc\examples,参考封装了一个类来做解码、缩放和编码的流程,代码如下图:
/*
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__
/*
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;
}