【FFmpeg】调用ffmpeg进行H264软编
从上面这一系列的函数调用来看,大致操作流程和数据走向大约是1.编码器的创建和初始化(avcodec_find_encoder)2.编码器上下文的创建和初始化(avcodec_alloc_context3)3.创建编码器输出信息,使用AVPacket进行存储(av_packet_alloc)4.打开编码器(avcodec_open2)5.创建编码器输入信息,使用AVFrame进行存储(av_fram
调用FFmpeg库实现264软件编码
====== 示例工程 ======
【FFmpeg】调用FFmpeg库实现264软解
1. FFmpeg的编译
FFmpeg在Windows下的编译参考:http://t.csdnimg.cn/BtAW5
本文使用的FFmpeg版本为7.0
2. 调用FFmpeg实现H264软编
参考FFmpeg当中的/doc/examples当中的video_encode.c文件,进行调用过程的理解和实现。
2.1 基本框架
根据/doc/examples当中的实现进行修改,包括:
1.增加pragma去除fopen的warning
2.增加extern调用ffmpeg库文件
3.将avcodec_find_encoder_by_name修改成avcodec_find_encoder,并将编码器类型设置为AV_CODEC_ID_H264,即264编码器
4.增加error code的打印,可以查阅其对应的错误信息
在编码过程当中,使用了如下的函数
函数名 | 作用 |
---|---|
avcodec_find_encoder_by_name | 根据名称查找编码器,输入为const char*,返回为AVCodec,AVCodec记录了编码器信息 |
avcodec_find_encoder | 根据ID查找编码器,输入为AVCodecID,返回为AVCodec,记录了编码器信息 |
avcodec_alloc_context3 | 创建codec的上下文信息,输入为AVCodec,返回为AVCodecContext,其记录了编码过程上下文的流信息 |
av_packet_alloc | 创建数据包packet,并且初始化为默认值,返回为AVPacket;该结构存储压缩之后的数据。通常由解码器导出,然后作为输入传递给解码器,或者作为编码器的输出接收,然后传递给解码器 |
avcodec_open2 | 打开编码器,输入为AVCodec,返回为ret。该函数初始化了codec线程和配置 |
av_frame_alloc | 创建frame,返回为AVFrame。这个结构描述待编码的原始的音频或视频数据 |
av_frame_get_buffer | 获取buffer,输入为AVFrame,输出为ret。为音频或视频数据分配新的缓冲区 |
av_frame_make_writable | 使得当前帧变为可写状态,输入为AVFrame,输出为ret |
avcodec_send_frame | 将frame送入到编码器当中进行编码,输入为AVCodecContext和AVFrame,输出为ret |
avcodec_receive_packet | 接收编码器已编码信息,输入为AVCodecContext和AVPacket,输出为ret |
av_packet_unref | 释放packet的缓冲区,但不会释放packet结构体本身 |
av_frame_free | 释放frame的缓冲区以及结构体本身 |
av_packet_free | av_packet_free以及结构体本身 |
从上面这一系列的函数调用来看,大致操作流程和数据走向大约是
1.编码器的创建和初始化(avcodec_find_encoder)
2.编码器上下文的创建和初始化(avcodec_alloc_context3)
3.创建编码器输出信息,使用AVPacket进行存储(av_packet_alloc)
4.打开编码器(avcodec_open2)
5.创建编码器输入信息,使用AVFrame进行存储(av_frame_alloc)
6.获取编码器输入信息的数据Buffer(av_frame_get_buffer)
7.将输入的信息(帧)设置为可写模型(av_frame_make_writable)
8.准备输入信息(帧),这里实现的是使用固定数值,仅做测试(YUV三通道分别是240,128,64)
9.进入内部编码流程(调用encode函数)
10.将帧送入到编码器当中进行编码,数据信息的载体是AVFrame,AVCodecContext记录了编码流信息(avcodec_send_frame)
11.将已编码的帧拿出来,数据信息的载体是AVPacket(avcodec_receive_packet)
另外,unref和free的含义各不同,如果是在程序执行的内部,如encode这种内部编码函数,使用unref仅释放内存但不释放结构体,在最后主函数执行的结尾进行结构体的释放。
2.2 代码实现
// video_implementation.cpp : ���ļ����� "main" ����������ִ�н��ڴ˴���ʼ��������
//
// �������fopen���ֵ�warning
#pragma warning(disable : 4996)
#include "video_encode.h"
#ifdef _WIN32
//Windows
extern "C" // 在C++文件中调用C文件需要使用,ffmpeg是使用C实现的
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#include "libavutil/imgutils.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/opt.h"
#ifdef __cplusplus
};
#endif
#endif
static void encode_internal(AVCodecContext* avcodec_ctx, AVFrame* av_frm, AVPacket* av_pkt, FILE* out_file)
{
int ret;
/* Send frame to encoder */
// ret 返回值的几种情况
// EAGAIN 11 /* Try again */
// ENOMEM 12 /* Out of memory */
// EINVAL 22 /* Invalid argument */, 可能是编码器没有打开,或者是初始化时使用的是解码器
// AVERROR_EOF /* End of File */
// 0 /* Success */
ret = avcodec_send_frame(avcodec_ctx, av_frm);
if (ret < 0) {
fprintf(stderr, "Error! fail to send frame, error code:%d", ret);
exit(1);
}
while (ret >= 0) {
/* Receive encoded frame */
// EAGAIN 11 /* Try again */
// EINVAL 22 /* Invalid argument */, 可能是编码器没有打开,或者是初始化时使用的是解码器
// AVERROR_EOF /* End of File */
ret = avcodec_receive_packet(avcodec_ctx, av_pkt);
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
return;
}
else if (ret < 0) {
fprintf(stderr, "Error! during encoding, error code:%d", ret);
exit(1);
}
// unref将实例对应的内存释放掉
// packet_unref针对于packet,每个函数都有一个对应的
printf("Write packet %3" PRId64" (size=%5d)\n", av_pkt->pts, av_pkt->size);
fwrite(av_pkt->data, 1, av_pkt->size, out_file);
av_packet_unref(av_pkt);
}
}
int encode(const char* in_file, const char* out_file)
{
const AVCodec* avcodec;
AVCodecContext* avcodec_ctx = NULL;
AVPacket* av_pkt;
AVFrame* av_frm;
//const char* file_name;
//const char* codec_name;
uint8_t endcode[] = { 0, 0, 1, 0xb7 };
int ret = 0;
//if (argc <= 2) {
// fprintf(stderr, "Usage: %s <output file> <codec name>\n", argv[0]);
// exit(0);
//}
//file_name = argv[1];
//codec_name = argv[2];
/* create codec */
// codec类型设置为264
avcodec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!avcodec) {
fprintf(stderr, "Error! can not find encoder");
exit(1);
}
/* create context */
// 创建编码器上下文信息
avcodec_ctx = avcodec_alloc_context3(avcodec);
if (!avcodec_ctx) {
fprintf(stderr, "Error! can not alloc avcodec");
exit(1);
}
/* create packet */
// 创建packet数据类型
av_pkt = av_packet_alloc();
if (!av_pkt) {
fprintf(stderr, "Error! alloc pkt failed");
exit(1);
}
/* ctx init */
avcodec_ctx->width = 1920;
avcodec_ctx->height = 1200;
avcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
/*
time_base是一种时间戳计量基准,用于在多个结构体当中的time_base当中设置一个基准
ffmpeg当中不同的结构体所使用的的time_base不统一,通过设置一个time_base,作为各个结构体之间数据转换的标准
*/
avcodec_ctx->time_base.den = 1;
avcodec_ctx->time_base.num = 25;
avcodec_ctx->framerate.den = 25;
avcodec_ctx->framerate.num = 1;
/* set context preset level */
if (avcodec->id == AV_CODEC_ID_H264) {
// preset = slow
// zerolatency are commonly used in conference meeting
av_opt_set(avcodec_ctx->priv_data, "preset", "slow", 0);
av_opt_set(avcodec_ctx->priv_data, "tune", "zerolatency", 0);
}
/* open codec */
ret = avcodec_open2(avcodec_ctx, avcodec, NULL);
if (ret < 0) {
fprintf(stderr, "Error! open avcodec failed, error code:%d", ret);
exit(1);
}
/* file open */
FILE* fp_in = fopen(in_file, "rb");
if (!fp_in) {
fprintf(stderr, "Error on opening input file\n");
exit(1);
}
FILE* fp_out = fopen(out_file, "wb");
if (!fp_out) {
fprintf(stderr, "Error: open file failed");
exit(1);
}
av_frm = av_frame_alloc();
if (!av_frm) {
fprintf(stderr, "Error: alloc frame failed");
exit(1);
}
av_frm->width = avcodec_ctx->width;
av_frm->height = avcodec_ctx->height;
av_frm->format = avcodec_ctx->pix_fmt;
/* get buffer */
ret = av_frame_get_buffer(av_frm, 0);
if (ret < 0) {
fprintf(stderr, "Error: alloc frame data failed, error code:%d", ret);
exit(1);
}
int x = 0;
int y = 0;
int frame_number = 10;
int picture_size = 0;
uint8_t* picture_buf;
picture_size = av_image_get_buffer_size(avcodec_ctx->pix_fmt, avcodec_ctx->width, avcodec_ctx->height, 1);
picture_buf = (uint8_t*)av_malloc(picture_size);
// av_image_fill_arrays(av_frm->data, av_frm->linesize, picture_buf, avcodec_ctx->pix_fmt, avcodec_ctx->width, avcodec_ctx->height, 1);
int y_size = avcodec_ctx->width * avcodec_ctx->height;
for (int i = 0; i < frame_number; i++) {
fflush(stdout);
/* Make sure the frame data is writable.
On the first round, the frame is fresh from av_frame_get_buffer()
and therefore we know it is writable.
But on the next rounds, encode() will have called
avcodec_send_frame(), and the codec may have kept a reference to
the frame in its internal structures, that makes the frame
unwritable.
av_frame_make_writable() checks that and allocates a new buffer
for the frame only if necessary.
*/
ret = av_frame_make_writable(av_frm);
if (ret < 0) {
fprintf(stderr, "Error: can not make frame writable");
exit(1);
}
/* Prepare dummy image */
/* Componet Y */
//for (y = 0; y < avcodec_ctx->height; y++) {
// for (x = 0; x < avcodec_ctx->width; x++) {
// av_frm->data[0][y * av_frm->linesize[0] + x] = 240;
// }
//}
///* Component Cb and Cr */
//for (y = 0; y < avcodec_ctx->height / 2; y++) {
// for (x = 0; x < avcodec_ctx->width / 2; x++) {
// av_frm->data[1][y * av_frm->linesize[1] + x] = 128;
// av_frm->data[2][y * av_frm->linesize[2] + x] = 64;
// }
//}
if (fread(picture_buf, 1, y_size * 3 / 2, fp_in) <= 0)
{
printf("Failed to read raw data.\n");
return -1;
}
else if (feof(fp_in))
{
break;
}
av_frm->data[0] = picture_buf; // Y
av_frm->data[1] = picture_buf + y_size; // U
av_frm->data[2] = picture_buf + y_size * 5 / 4; // V
av_frm->pts = i;
/* encode the img */
encode_internal(avcodec_ctx, av_frm, av_pkt, fp_out);
fprintf(stdout, "encoded frame num:%d\n", i);
}
/* flush the encoder */
encode_internal(avcodec_ctx, NULL, av_pkt, fp_out);
// Add end code if use MEPG format
//if (avcodec->id == AV_CODEC_ID_MPEG1VIDEO || avcodec->id == AV_CODEC_ID_MPEG2VIDEO || avcodec->id == AV_CODEC_ID_H264)
// fwrite(endcode, 1, sizeof(endcode), fp_out);
//fclose(fp_out);
// free memory
avcodec_free_context(&avcodec_ctx);
av_frame_free(&av_frm);
av_packet_free(&av_pkt);
return 0;
}
2.3 测试结果
[libx264 @ 000001e7b1fc6000] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
[libx264 @ 000001e7b1fc6000] profile High, level 5.0, 4:2:0, 8-bit
[libx264 @ 000001e7b1fc6000] frame I:1 Avg QP: 5.00 size: 1063
[libx264 @ 000001e7b1fc6000] frame P:24 Avg QP: 0.00 size: 77
[libx264 @ 000001e7b1fc6000] mb I I16..4: 100.0% 0.0% 0.0%
[libx264 @ 000001e7b1fc6000] mb P I16..4: 0.0% 0.0% 0.0% P16..4: 0.0% 0.0% 0.0% 0.0% 0.0% skip:100.0%
[libx264 @ 000001e7b1fc6000] 8x8 transform intra:0.0%
[libx264 @ 000001e7b1fc6000] coded y,uvDC,uvAC intra: 0.0% 0.0% 0.0% inter: 0.0% 0.0% 0.0%
[libx264 @ 000001e7b1fc6000] i16 v,h,dc,p: 99% 0% 1% 0%
[libx264 @ 000001e7b1fc6000] i8c dc,h,v,p: 100% 0% 0% 0%
[libx264 @ 000001e7b1fc6000] Weighted P-Frames: Y:0.0% UV:0.0%
[libx264 @ 000001e7b1fc6000] kb/s:0.04
Github: https://github.com/DoFulangChen/video_implementation.git
![Logo](https://devpress.csdnimg.cn/79de2bf0b7994defa4242ef90d5513fa.jpg)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)