Files
M5Stack_Linux_Libs/components/ffmpeg_vcode/include/vcodec.hpp
T

719 lines
22 KiB
C++
Raw Normal View History

2024-04-10 12:21:46 +08:00
/*
* @Copyright: iflytek
* @Autor: zghong
* @Date: 2020-04-07 12:18:54
* @Description: 利用FFmpeg,对常见的编码格式文件进行编解码
*
* 编解码封装在vcodec类中,接口为encode()和decode()函数。
* 使用时需要指出<源文件><目标文件>及<编解码器名>。其中编解码器名可以通过`ffmpeg -encoders, ffmpeg -decoders`查看。
*/
#ifndef _VCODEC_HPP
#define _VCODEC_HPP
#include <string>
#include <streambuf>
#include <fstream>
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
#include "libswscale/swscale.h"
#ifdef __cplusplus
}
#endif
/**********************************************************************
* vcodec类定义部分
*
* 进行视频编解码类,实例化时需要指出<源文件>,<目标文件>及<编解码器名>
* 对外的接口如下:
* 1、encode(),编码
* 2、decode(),解码
**********************************************************************
*/
class vcodec
{
public:
enum
{
FILE_READ_CALL,
FILE_READ_WRITE,
FILE_READ_WRITE_CALL,
IOSTREAM_READ_CALL,
IOSTREAM_READ_WRITE,
IOSTREAM_READ_WRITE_CALL,
CUSTOM_READ_CALL,
CUSTOM_READ_WRITE,
CUSTOM_READ_WRITE_CALL,
};
typedef void (*callback_get_ones_frame_t)(size_t, void *, size_t);
typedef int (*callback_inface_fun_t)(void *, size_t);
typedef struct _frame_rgb_data
{
char *data;
size_t len;
size_t h;
size_t w;
} frame_rgb_data_t;
frame_rgb_data_t rgb;
vcodec(std::string in_path, std::string _codec_name, callback_get_ones_frame_t callback);
vcodec(std::string in_path, std::string out_path, std::string _codec_name);
vcodec(std::string in_path, std::string out_path, std::string _codec_name, callback_get_ones_frame_t callback);
vcodec(std::iostream *sin, std::string _codec_name, callback_get_ones_frame_t callback);
vcodec(std::iostream *sin, std::iostream *sout, std::string _codec_name);
vcodec(std::iostream *sin, std::iostream *sout, std::string _codec_name, callback_get_ones_frame_t callback);
vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, std::string _codec_name, callback_get_ones_frame_t callback);
vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string _codec_name);
vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string _codec_name, callback_get_ones_frame_t callback);
void set_callback(callback_get_ones_frame_t callback);
callback_inface_fun_t custom_write, custom_read, custom_eof;
int yuv2rgb(void *_frame, int mode);
int yuv2rgb(void *_frame);
void data_init(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string in_path, std::string out_path, std::iostream *sin, std::iostream *sout, std::string _codec_name, callback_get_ones_frame_t callback);
void clear(void);
~vcodec();
int encode();
int decode();
private:
int mode;
std::fstream fin;
std::fstream fout;
int use_file;
std::iostream *_in;
std::iostream *_out;
std::string codec_name;
AVPicture g_oPicture = {0};
struct SwsContext *g_pScxt = NULL;
AVFrame *frame;
AVPacket *packet;
AVCodec *codec;
AVCodecContext *codec_ctx;
AVCodecParserContext *parser_ctx;
callback_get_ones_frame_t fun;
int encode_frame2packet();
int decode_packet2frame();
int c_write(void *buf, size_t n);
int c_read(void *buf, size_t n);
int c_eof(void *buf, size_t n);
};
/**********************************************************************
* vcodec类实现部分
**********************************************************************
*/
void vcodec::data_init(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string in_path, std::string out_path, std::iostream *sin, std::iostream *sout, std::string _codec_name, callback_get_ones_frame_t callback)
{
this->custom_read = read;
this->custom_eof = eof;
this->custom_write = write;
this->rgb.data = NULL;
this->_in = sin;
this->_out = sout;
this->fun = callback;
this->use_file = 0;
this->codec_name = _codec_name;
if (in_path.length() != 0)
{
this->fin.open(in_path.c_str(), std::ios::binary | std::ios::in);
if (!this->fin.is_open())
{
fprintf(stderr, "[ERROR] Failed to open <%s>\n", in_path.c_str());
this->fin.close();
exit(-1);
}
this->_in = &this->fin;
this->use_file = 1;
}
if (out_path.length() != 0)
{
this->fout.open(out_path.c_str(), std::ios::binary | std::ios::out | std::ios::trunc);
if (!this->fout.is_open())
{
this->fin.close();
this->fout.close();
fprintf(stderr, "[ERROR] Failed to open <%s>\n", out_path.c_str());
exit(-1);
}
this->_out = &this->fout;
this->use_file = 2;
}
}
vcodec::vcodec(std::string in_path, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path = in_path;
std::string __out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = FILE_READ_CALL;
}
/**
* @brief: vcodec类构造函数
* @param in_path 输入文件路径
* @param out_path 输出文件路径
* @param codec_name 编解码器名
*/
vcodec::vcodec(std::string in_path, std::string out_path, std::string _codec_name)
{
std::string __in_path = in_path;
std::string __out_path = out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = NULL;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = FILE_READ_WRITE;
}
vcodec::vcodec(std::string in_path, std::string out_path, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path = in_path;
std::string __out_path = out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = FILE_READ_WRITE_CALL;
}
vcodec::vcodec(std::iostream *sin, std::iostream *sout, std::string _codec_name)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = sin;
std::iostream *__sout = sout;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = NULL;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = IOSTREAM_READ_WRITE;
}
vcodec::vcodec(std::iostream *sin, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = sin;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = IOSTREAM_READ_CALL;
}
vcodec::vcodec(std::iostream *sin, std::iostream *sout, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = sin;
std::iostream *__sout = sout;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(NULL, NULL, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = IOSTREAM_READ_WRITE_CALL;
}
vcodec::vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(read, eof, NULL, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = CUSTOM_READ_CALL;
}
vcodec::vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string _codec_name)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = NULL;
data_init(read, eof, write, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = CUSTOM_READ_WRITE;
}
vcodec::vcodec(callback_inface_fun_t read, callback_inface_fun_t eof, callback_inface_fun_t write, std::string _codec_name, callback_get_ones_frame_t callback)
{
std::string __in_path;
std::string __out_path;
std::iostream *__sin = NULL;
std::iostream *__sout = NULL;
std::string __codec_name = _codec_name;
callback_get_ones_frame_t __callback = callback;
data_init(read, eof, write, __in_path, __out_path, __sin, __sout, __codec_name, __callback);
this->mode = CUSTOM_READ_WRITE_CALL;
}
void vcodec::set_callback(callback_get_ones_frame_t callback)
{
this->fun = callback;
}
int vcodec::yuv2rgb(void *_frame, int mode)
{
AVFrame *pFrame = (AVFrame *)_frame;
int width = pFrame->width;
int height = pFrame->height;
if (g_pScxt == NULL)
{
g_pScxt = sws_getContext(width, height, (AVPixelFormat)pFrame->format,
width, height, (AVPixelFormat)mode,
NULL, NULL, NULL, NULL);
// AVFrame需要填充,还是这个方便。
avpicture_alloc(&g_oPicture, (AVPixelFormat)mode, width, height);
}
int num_bytes = av_image_get_buffer_size((AVPixelFormat)mode, width, height, 32);
int retsws = sws_scale(g_pScxt, pFrame->data, pFrame->linesize, 0, height,
g_oPicture.data, g_oPicture.linesize);
// //注意,RGB数据是这样复制的
if (this->rgb.data == NULL)
{
this->rgb.data = (char *)malloc(num_bytes);
}
if (rgb.len != num_bytes)
{
free(this->rgb.data);
this->rgb.data = (char *)malloc(num_bytes);
}
memcpy(this->rgb.data, g_oPicture.data[0], num_bytes);
rgb.w = width;
rgb.h = height;
rgb.len = num_bytes;
return 0;
}
int vcodec::yuv2rgb(void *_frame)
{
return yuv2rgb(_frame, AV_PIX_FMT_RGB24);
}
void vcodec::clear(void)
{
switch (this->use_file)
{
case 2:
this->fout.close();
case 1:
this->fin.close();
break;
default:
break;
}
if (this->g_pScxt != NULL)
{
avpicture_free(&this->g_oPicture);
this->g_oPicture = {0};
sws_freeContext(this->g_pScxt);
this->g_pScxt = NULL;
free(this->rgb.data);
this->rgb.data = NULL;
}
}
/**
* @brief: vcodec类析构函数
*/
vcodec::~vcodec()
{
clear();
}
/**
* @brief: 使用给定的编解码器对输入文件编码,将结果写入输出文件
* @return: 0 for ok, -1 for error
*/
int vcodec::encode()
{
// allocation
this->frame = av_frame_alloc();
if (!this->frame)
{
fprintf(stderr, "[ERROR] Failed to allocate frame\n");
return -1;
}
this->packet = av_packet_alloc();
if (!this->packet)
{
fprintf(stderr, "[ERROR] Failed to allocate packet\n");
return -1;
}
this->codec = avcodec_find_encoder_by_name(this->codec_name.c_str());
if (!this->codec)
{
fprintf(stderr, "[ERROR] Failed to find codec\n");
return -1;
}
this->codec_ctx = avcodec_alloc_context3(this->codec);
if (!this->codec_ctx)
{
fprintf(stderr, "[ERROR] Faild to allocate codec context\n");
return -1;
}
// encoder settings
this->codec_ctx->codec_id = this->codec->id;
this->codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
this->codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
this->codec_ctx->width = 640;
this->codec_ctx->height = 480;
this->codec_ctx->time_base = (AVRational){1, 30}; // fps
// vbr设置
// int64_t br = 468000;
// this->codec_ctx->bit_rate = br;
// this->codec_ctx->rc_min_rate = br;
// this->codec_ctx->rc_max_rate = br;
// this->codec_ctx->bit_rate_tolerance = br;
// this->codec_ctx->rc_buffer_size = br;
// this->codec_ctx->rc_initial_buffer_occupancy = this->codec_ctx->rc_buffer_size * 3 / 4;
this->codec_ctx->bit_rate = 468000;
this->codec_ctx->gop_size = 250;
this->codec_ctx->max_b_frames = 0; // no b-frame
if (this->codec_ctx->codec_id == AV_CODEC_ID_H264)
{
av_opt_set(this->codec_ctx->priv_data, "preset", "slow", 0);
av_opt_set(this->codec_ctx->priv_data, "tune", "zerolatency", 0);
}
else if (this->codec_ctx->codec_id == AV_CODEC_ID_H265)
{
av_opt_set(this->codec_ctx->priv_data, "preset", "ultrafast", 0);
av_opt_set(this->codec_ctx->priv_data, "tune", "zero-latency", 0);
}
this->frame->format = this->codec_ctx->pix_fmt;
this->frame->width = this->codec_ctx->width;
this->frame->height = this->codec_ctx->height;
// open the codec
if (avcodec_open2(this->codec_ctx, this->codec, NULL) < 0)
{
fprintf(stderr, "[ERROR] Failed to open codec\n");
return -1;
}
else
{
fprintf(stdout, "[INFO] Open codec successfully\n");
}
// encode 1 second of video
int frame_size = av_image_get_buffer_size(this->codec_ctx->pix_fmt, this->codec_ctx->width, this->codec_ctx->height, 1);
uint8_t *frame_buf = (uint8_t *)av_malloc(frame_size);
int y_size = this->codec_ctx->width * this->codec_ctx->height; // size of Y
av_image_fill_arrays(this->frame->data, this->frame->linesize, frame_buf, this->codec_ctx->pix_fmt, this->codec_ctx->width, this->codec_ctx->height, 1);
int i = 0;
while (!this->c_eof(NULL, 0))
{
int num = this->c_read(frame_buf, (y_size * 3 / 2));
if (num < 0)
{
break;
}
else if (num == 0)
{
continue;
}
// read yuv data from source file into AVFrame
this->frame->data[0] = frame_buf; // Y
this->frame->data[1] = frame_buf + y_size; // U
this->frame->data[2] = frame_buf + y_size * 5 / 4; // V
this->frame->pts = i++;
// encode frame into packet
if (this->encode_frame2packet() != 0)
{
return -1;
}
}
// flush the encoder
this->frame = NULL;
this->encode_frame2packet();
av_frame_free(&this->frame);
av_packet_free(&this->packet);
avcodec_free_context(&this->codec_ctx);
fprintf(stdout, "[SUCCESS] Encode file successfully\n");
return 0;
}
/**
* @brief: 使用给定的编解码器对输入文件解码,将结果写入输出文件
* @return: 0 for ok, -1 for error
*/
int vcodec::decode()
{
// allocation
this->frame = av_frame_alloc();
if (!this->frame)
{
fprintf(stderr, "[ERROR] Failed to allocate frame\n");
return -1;
}
this->packet = av_packet_alloc();
if (!this->packet)
{
fprintf(stderr, "[ERROR] Failed to allocate packet\n");
return -1;
}
this->codec = avcodec_find_decoder_by_name(this->codec_name.c_str());
if (!this->codec)
{
fprintf(stderr, "[ERROR] Failed to find codec\n");
return -1;
}
this->codec_ctx = avcodec_alloc_context3(this->codec);
if (!this->codec_ctx)
{
fprintf(stderr, "[ERROR] Faild to allocate codec context\n");
return -1;
}
this->parser_ctx = av_parser_init(this->codec->id);
if (!this->parser_ctx)
{
fprintf(stderr, "[ERROR] Failed to find parser of codec\n");
return -1;
}
// open the codec
if (avcodec_open2(this->codec_ctx, this->codec, NULL) < 0)
{
fprintf(stderr, "[ERROR] Failed to open codec\n");
return -1;
}
else
{
fprintf(stdout, "[INFO] Open codec successfully\n");
}
// start to decode
int input_buf_size = 4096;
uint8_t input_buf[input_buf_size + AV_INPUT_BUFFER_PADDING_SIZE] = {0};
while (!this->c_eof(NULL, 0))
{
// read source data
// this->_in->read((char *)input_buf, input_buf_size);
// int read_size = this->_in->gcount();
int read_size = this->c_read(input_buf, input_buf_size);
if (read_size < 0)
{
break;
}
else if (read_size == 0)
{
continue;
}
uint8_t *input_buf_pos = input_buf;
while (read_size > 0)
{
// parse data into packet
int parsed_size = av_parser_parse2(this->parser_ctx, this->codec_ctx,
&this->packet->data, &this->packet->size, input_buf_pos, read_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
if (parsed_size < 0)
{
fprintf(stderr, "Error while parsing\n");
return -1;
}
// update position of buffer and size of remained data
input_buf_pos += parsed_size;
read_size -= parsed_size;
if (this->packet->size)
{
// decode packet into frame
if (this->decode_packet2frame() != 0)
{
fprintf(stderr, "Error while packet2frame\n");
return -1;
}
}
}
}
fprintf(stdout, "[SUCCESS] Decode file successfully\n");
return 0;
}
/**
* @brief: 将数据帧编码成数据包
* @return: 0 for ok, -1 for error
*/
int vcodec::encode_frame2packet()
{
int ret = avcodec_send_frame(this->codec_ctx, this->frame);
if (ret < 0)
{
fprintf(stderr, "[ERROR] Failed to send a frame for encoding\n");
return -1;
}
while (ret >= 0)
{
ret = avcodec_receive_packet(this->codec_ctx, this->packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
return 0;
}
else if (ret < 0)
{
fprintf(stderr, "[ERROR] Error during encoding, code <%d>\n", ret);
return -1;
}
static size_t ecount = 0;
if (this->fun)
this->fun(ecount++, this->packet->data, this->packet->size);
if (this->_out)
this->c_write((this->packet->data), (this->packet->size));
// this->_out->write((char *)this->packet->data, this->packet->size);
fprintf(stdout, "[INFO] Saving packet %3" PRId64 " (size=%5d)\n", this->packet->pts, this->packet->size);
av_packet_unref(this->packet);
}
return 0;
}
/**
* @brief: 将数据包解码成数据帧
* @return: 0 for ok, -1 for error
*/
int vcodec::decode_packet2frame()
{
int ret = avcodec_send_packet(this->codec_ctx, this->packet);
if (ret < 0)
{
fprintf(stderr, "[ERROR] Failed to send a packet for decoding\n");
return -1;
}
while (ret >= 0)
{
ret = avcodec_receive_frame(this->codec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
return 0;
}
else if (ret < 0)
{
fprintf(stderr, "[ERROR] Error during decoding, code <%d>\n", ret);
return -1;
}
static size_t dcount = 0;
if (this->fun)
this->fun(dcount++, frame, 0);
// save the frame into file
// Y, U, V
if (this->_out)
{
for (int i = 0; i < frame->height; i++)
{
// this->_out->write((char *)(frame->data[0] + frame->linesize[0] * i), frame->width);
this->c_write((frame->data[0] + frame->linesize[0] * i), (frame->width));
}
for (int i = 0; i < frame->height / 2; i++)
{
// this->_out->write((char *)(frame->data[1] + frame->linesize[1] * i), frame->width / 2);
this->c_write((frame->data[1] + frame->linesize[1] * i), (frame->width / 2));
}
for (int i = 0; i < frame->height / 2; i++)
{
// this->_out->write((char *)(frame->data[2] + frame->linesize[2] * i), frame->width / 2);
this->c_write((frame->data[2] + frame->linesize[2] * i), (frame->width / 2));
}
}
fprintf(stdout, "[INFO] Saving frame %3d\n", this->codec_ctx->frame_number);
av_frame_unref(this->frame);
}
return 0;
}
int vcodec::c_write(void *buf, size_t n)
{
switch (mode)
{
case FILE_READ_WRITE:
case FILE_READ_WRITE_CALL:
case IOSTREAM_READ_WRITE:
case IOSTREAM_READ_WRITE_CALL:
{
this->_out->write((char *)buf, n);
}
break;
case CUSTOM_READ_WRITE:
case CUSTOM_READ_WRITE_CALL:
{
this->custom_write(buf, n);
}
break;
default:
break;
}
return 0;
}
int vcodec::c_read(void *buf, size_t n)
{
switch (mode)
{
case FILE_READ_CALL:
case FILE_READ_WRITE:
case FILE_READ_WRITE_CALL:
case IOSTREAM_READ_CALL:
case IOSTREAM_READ_WRITE:
case IOSTREAM_READ_WRITE_CALL:
{
this->_in->read((char *)buf, n);
int mk = this->_in->gcount();
return mk;
}
break;
case CUSTOM_READ_CALL:
case CUSTOM_READ_WRITE:
case CUSTOM_READ_WRITE_CALL:
{
return this->custom_read(buf, n);
}
break;
default:
break;
}
}
int vcodec::c_eof(void *buf, size_t n)
{
switch (mode)
{
case FILE_READ_CALL:
case FILE_READ_WRITE:
case FILE_READ_WRITE_CALL:
case IOSTREAM_READ_CALL:
case IOSTREAM_READ_WRITE:
case IOSTREAM_READ_WRITE_CALL:
{
return (int)this->_in->eof();
}
break;
case CUSTOM_READ_CALL:
case CUSTOM_READ_WRITE:
case CUSTOM_READ_WRITE_CALL:
{
return this->custom_eof(NULL, 0);
}
break;
default:
break;
}
}
#endif