学习FFMPEG有段时间了,FFMPEG对通用的视频编解码做了统一接口处理的抽象,比如在解码处理时,无须关心其具体的编解码格式,仅需关心其pixfmt即可。
FFMPEG使用时需要关心下面这些核心的结构体。
AVFormatContext // 封视频格装上下文,是处理编封装功能的结构体
AVCodecContext // 解码器上下文,是编解码功能的结构体,存储了gop/definition/pixfmt/profile/bit_rate等参数
AVCodec // 存储编解码器信息的结构体
AVFrame // 存储解码后的视音频数据,包括yuv/pcm/宏块数据/运动矢量等信息
AVPacket // 存储编码后的数据
SwsContext // 格式转换
AVStream // 存储每一个视频/音频流信息的结构体
下面代码完成如下功能:
#include
#include "math.h"
#include
#include
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "stdint.h"
#ifndef UINT64_C
#define UINT64_C(value)__CONCAT(value,ULL)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __cplusplus
}
#endif
int ffmpeg_video_dec(char* path_in, char* path_yuv_out)
{
AVFormatContext* AFCtx_p; // 解封装上下文,是解封装功能的结构体
AVCodecContext* ACCtx_p; // 解码器上下文,是编解码功能的结构体,存储了gop/definition/pixfmt/profile/bit_rate等参数
AVCodec* codec_p; // 存储编解码器信息的结构体
AVFrame* pFrame; // 存储解码后的视音频数据,包括yuv/pcm/宏块数据/运动矢量等信息
AVFrame* pFrameyuv;
AVPacket* packet;
struct SwsContext* img_convert_ctx;
AVStream* stream; // 存储每一个视频/音频流信息的结构体
int videoindex = -1;
int y_size;
FILE* fp_yuv = fopen(path_yuv_out, "w+");
av_register_all(); // 初始化FFMPEG,注册所有模块,调用了这个才能正常使用编码器和解码器
// Allocate an AVFormatContext.
AFCtx_p = avformat_alloc_context(); // 创建AFCtx_p
avformat_network_init(); // 初始化网络库
// 打开stream文件并且read header,将文件信息存入解封装上下文
int open_stream_ret = avformat_open_input(&AFCtx_p, path_in, NULL, NULL);
if (open_stream_ret != 0)
{
fprintf(stderr, "open failed[%d].\n", open_stream_ret);
return -1;
}
// 获取视频流信息
int find_stream_ret = avformat_find_stream_info(AFCtx_p, NULL);
if (find_stream_ret < 0)
{
fprintf(stderr, "stream find failed[%d].\n", find_stream_ret);
return -1;
}
// dump调试信息
av_dump_format(AFCtx_p, 0, path_in, 0);
// 获取metadata字典
AVDictionaryEntry* entry = nullptr;
// webm透明通道标识
int alpha_flag = 0;
//打开视频并且获取了视频流,设置视频索引默认值
for (int i = 0; i < AFCtx_p->nb_streams; i++)
{
if (AFCtx_p->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
{
fprintf(stderr, "nb_streams[%d], videoindex[%d].\n", AFCtx_p->nb_streams, i);
videoindex = i;
}
stream = AFCtx_p->streams[i];
while ((entry = av_dict_get(AFCtx_p->streams[i]->metadata, "", entry, AV_DICT_IGNORE_SUFFIX))) {
if((!strcmp(entry->key, "ALPHA_MODE")) && (!strcmp(entry->value, "1"))) {
alpha_flag = 1;
fprintf(stderr, "webm alpha.\n");
} else {
alpha_flag = 0;
}
fprintf(stdout, "key: %s, value: %s\n", entry->key, entry->value);
}
fprintf(stderr, "stream[%d], num[%d], den[%d], frame_num[%ld].\n", i, stream->avg_frame_rate.num, stream->avg_frame_rate.den, stream->nb_frames);
}
// 如果没有找到视频索引,说明不是一个视频文件
if (videoindex == -1)
{
fprintf(stderr, "not a video.\n");
return -1;
}
// 分配解码器上下文空间
ACCtx_p = avcodec_alloc_context3(NULL);
// 从封装上下文中获取编解码器上下文信息
int codec_get_ret = avcodec_parameters_to_context(ACCtx_p, AFCtx_p->streams[videoindex]->codecpar);
if (codec_get_ret < 0)
{
fprintf(stderr, "copy stream failed[%d].\n", codec_get_ret);
return -1;
}
// 查找解码器
fprintf(stderr, "codec_id[%d].\n", ACCtx_p->codec_id);
fprintf(stderr, "timebase[%d/%d].\n", ACCtx_p->pkt_timebase.num, ACCtx_p->pkt_timebase.den);
if(1 == alpha_flag) {
if(AV_CODEC_ID_VP8 == ACCtx_p->codec_id) {
codec_p = avcodec_find_decoder_by_name("libvpx"); // vp8-alpha
} else if(AV_CODEC_ID_VP9 == ACCtx_p->codec_id) {
codec_p = avcodec_find_decoder_by_name("libvpx-vp9"); // vp9-alpha
} else {
codec_p = avcodec_find_decoder(ACCtx_p->codec_id); // 不带alpha
}
} else {
// codec_p = avcodec_find_decoder_by_name("h264_cuvid");
codec_p = avcodec_find_decoder(ACCtx_p->codec_id); // 不带alpha
}
if (!codec_p)
{
fprintf(stderr, "find decoder error.\n");
return -1;
}
// 打开解码器
int open_codec_ret = avcodec_open2(ACCtx_p, codec_p, NULL);
if (open_codec_ret != 0)
{
fprintf(stderr, "open codec failed[%d].\n", open_codec_ret);
return -1;
}
// 分配AVPacket/AVFrame
packet = av_packet_alloc();
pFrame = av_frame_alloc();
pFrameyuv = av_frame_alloc();
// 获取转换后YUV数据的大小
int dst_width = ACCtx_p->width / 2 * 2;
int dst_height = ACCtx_p->height / 2 * 2;
int video_size = dst_width * dst_height;
uint8_t* buf = NULL;
// 裁剪图像
fprintf(stderr, "src_width[%d], src_height[%d], dst_width[%d], dst_height[%d], pix_fmt[%d].\n",
ACCtx_p->width, ACCtx_p->height, dst_width, dst_height, ACCtx_p->pix_fmt);
// 根据格式分配scale操作相关的上下文SwsContext
if((AV_PIX_FMT_YUV420P == ACCtx_p->pix_fmt && alpha_flag == 1) || AV_PIX_FMT_RGBA == ACCtx_p->pix_fmt || AV_PIX_FMT_ARGB == ACCtx_p->pix_fmt || AV_PIX_FMT_YUVA444P12LE == ACCtx_p->pix_fmt || AV_PIX_FMT_PAL8 == ACCtx_p->pix_fmt) {
fprintf(stderr, "this is rgba/yuva data, pixfmt[%d].\n", ACCtx_p->pix_fmt);
// 这种格式libvpx解码时会转换为yuva420
if((AV_PIX_FMT_YUV420P == ACCtx_p->pix_fmt && alpha_flag == 1)) {
img_convert_ctx = sws_getContext(ACCtx_p->width, ACCtx_p->height, AV_PIX_FMT_YUVA420P,
dst_width, dst_height, AV_PIX_FMT_YUVA420P, SWS_BICUBIC, NULL, NULL, NULL);
} else {
img_convert_ctx = sws_getContext(ACCtx_p->width, ACCtx_p->height, ACCtx_p->pix_fmt,
dst_width, dst_height, AV_PIX_FMT_YUVA420P, SWS_BICUBIC, NULL, NULL, NULL);
}
video_size = av_image_get_buffer_size(AV_PIX_FMT_YUVA420P, dst_width, dst_height, 1);
buf = (uint8_t*)av_malloc(video_size);
av_image_fill_arrays(pFrameyuv->data, pFrameyuv->linesize,
buf, AV_PIX_FMT_YUVA420P, dst_width, dst_height, 1);
} else {
fprintf(stderr, "this is rgb/yuv data, pixfmt[%d].\n", ACCtx_p->pix_fmt);
img_convert_ctx = sws_getContext(ACCtx_p->width, ACCtx_p->height, ACCtx_p->pix_fmt,
dst_width, dst_height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
video_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, dst_width, dst_height, 1);
buf = (uint8_t*)av_malloc(video_size);
av_image_fill_arrays(pFrameyuv->data, pFrameyuv->linesize,
buf, AV_PIX_FMT_YUV420P, dst_width, dst_height, 1);
}
if (!img_convert_ctx)
{
fprintf(stderr, "get swscale context failed.\n");
return -1;
}
// 循环读取帧数据并转换写入
while (av_read_frame(AFCtx_p, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
if (avcodec_send_packet(ACCtx_p, packet) != 0)
{
fprintf(stderr, "send video stream packet failed.\n");
return -1;
}
if (avcodec_receive_frame(ACCtx_p, pFrame) != 0)
{
fprintf(stderr, "receive video frame failed, status = %d.\n", avcodec_receive_frame(ACCtx_p, pFrame));
continue;
}
fprintf(stderr, "decoding frame %d.\n", ACCtx_p->frame_number);
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize,
0, ACCtx_p->height, pFrameyuv->data, pFrameyuv->linesize);
// 视频编辑即对pFrameyuv->data进行编辑
y_size = dst_width * dst_height;
fwrite(pFrameyuv->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrameyuv->data[1], 1, y_size/4, fp_yuv); //U
fwrite(pFrameyuv->data[2], 1, y_size/4, fp_yuv); //V
if(AV_PIX_FMT_YUVA420P == ACCtx_p->pix_fmt || AV_PIX_FMT_RGBA == ACCtx_p->pix_fmt || AV_PIX_FMT_ARGB == ACCtx_p->pix_fmt || AV_PIX_FMT_YUVA444P12LE == ACCtx_p->pix_fmt || AV_PIX_FMT_PAL8 == ACCtx_p->pix_fmt) {
fwrite(pFrameyuv->data[3], 1, y_size, fp_yuv); //A
}
fflush(fp_yuv);
}
}
fclose(fp_yuv);
av_free(buf);
av_frame_free(&pFrame);
av_frame_free(&pFrameyuv);
av_packet_free(&packet);
sws_freeContext(img_convert_ctx);
avcodec_free_context(&ACCtx_p);
avformat_close_input(&AFCtx_p);
avformat_free_context(AFCtx_p);
return 1;
}
// g++ test_gpu_dec.cpp -L /usr/local/lib -lavcodec -lavformat -lavutil -lswscale -I /usr/local/include -o test_dec
// ./test_enc xxx.h264 xxx.yuv 10
int main(int argc, char** argv)
{
if(argc < 3)
fprintf(stderr, "input argc not enough.\n");
for(int i = 0; i < atoi(argv[3]); i++) {
fprintf(stderr, "i[%d].\n", i);
ffmpeg_video_dec(argv[1], argv[2]);
}
return 0;
}
服务端使用g++编译,生成可执行文件,运行时,指定输入的h264文件、输出的yuv文件以及运行次数,这里运行10次,至此,解码部分测试完成。
g++ test_gpu_dec.cpp -L /usr/local/lib -lavcodec -lavformat -lavutil -lswscale -I /usr/local/include -o test_dec
./test_enc xxx.h264 xxx.yuv 10 # 调用10次