基础介绍
结构体和函数
AVCodec 编码器结构体
AVCodecContext 编码器上下文
AVFrame 解码后的帧
av_frame_alloc/av_frame_free()
avcodec_alloc_context3()
avcodec_free_context()
解码步骤
- 查找解码器(avcodec_find_decoder)
- 打开解码器(avcodec_open2)
- 解码(avcodec_decode_video2)
视频编码
指定输出文件为out.h264,编码器为libx264
伪造YUV420P数据进行测试
cpp
extern "C" {
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
#include <string>
using namespace std;
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out) {
int ret = -1;
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");
goto _END;
}
while (ret >= 0) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
return -1;
}
fwrite(pkt->data, pkt->size, 1, out);
av_packet_unref(pkt);
}
_END:
return 0;
}
int main(int argc, char* argv[]) {
av_log_set_level(AV_LOG_DEBUG);
string dst;
string codec_name;
int ret = -1;
const AVCodec *codec = nullptr;
AVCodecContext *codec_ctx = nullptr;
FILE *fp = nullptr;
AVFrame *frame = nullptr;
AVPacket *pkt = nullptr;
// 1. 输入参数
if (argc < 3) {
av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");
goto _ERROR;
}
dst = argv[1];
codec_name = argv[2];
// 2. 查找编码器
codec = avcodec_find_encoder_by_name(codec_name.c_str());
if (!codec) {
av_log(nullptr, AV_LOG_ERROR, "could not find encoder %s\n", codec_name.c_str());
goto _ERROR;
}
// 3. 创建编码器上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");
goto _ERROR;
}
// 4. 设置编码器参数
codec_ctx->width = 640;
codec_ctx->height = 480;
codec_ctx->bit_rate = 500000;
codec_ctx->time_base = (AVRational){1, 25};
codec_ctx->framerate = (AVRational){25, 1};
codec_ctx->gop_size = 10; // 每10帧一个关键帧
codec_ctx->max_b_frames = 1; // 一个gop允许1个B
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
// 设置264编码器的特殊参数
if (codec->id == AV_CODEC_ID_H264) {
// priv_data是编码器私有数据
// 通过它可以设置编码器私有参数
// 这些参数在不同编码器中是不一样的
av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);
}
// 5. 编码器与编码器上下文绑定到一起
ret = avcodec_open2(codec_ctx, codec, nullptr);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "could not open codec %s\n", codec_name.c_str());
goto _ERROR;
}
// 6. 创建输出文件
fp = fopen(dst.c_str(), "wb+");
if (!fp) {
av_log(nullptr, AV_LOG_ERROR, "could not open file %s\n", dst.c_str());
goto _ERROR;
}
// 7. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");
goto _ERROR;
}
frame->width = codec_ctx->width;
frame->height = codec_ctx->height;
frame->format = codec_ctx->pix_fmt;
// 真正的像素数据分配,并与AVFrame绑定,后面设置为0,会自动根据cpu对齐
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate frame data\n");
goto _ERROR;
}
// 8. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");
goto _ERROR;
}
// 9. 生成视频内容
for (int i = 0; i < 25; ++i) {
// 确保frame是否可写
ret = av_frame_make_writable(frame);
if (ret < 0) {
break;
}
// Y分量
for (int y = 0; y < codec_ctx->height; y++) {
for (int x = 0; x < codec_ctx->width; x++) {
frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;
}
}
// UV分量
for (int y = 0; y < codec_ctx->height / 2; y++) {
for (int x = 0; x < codec_ctx->width / 2; x++) {
// U分量:128是黑色
frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;
// V分量:64是黑色
frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;
// 10. 编码
ret = encode(codec_ctx, frame, pkt, fp);
}
// 10. 编码 输入空帧目的是输出队列中剩余的数据
encode(codec_ctx, nullptr, pkt, fp);
_ERROR:
if (fp) {
fclose(fp);
fp = nullptr;
}
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
if (frame) {
av_frame_free(&frame);
frame = nullptr;
}
if (pkt) {
av_packet_free(&pkt);
pkt = nullptr;
}
return 0;
}
音频编码
与视频编码不同的内容:
- 编码器参数不一样
- 创建的帧的参数不一样
- 构建音频的方式与视频的方式差距很大 但是先不用管
cpp
extern "C" {
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
}
#include <string>
using namespace std;
static int select_best_samplerate(const AVCodec *codec) {
const int *p = codec->supported_samplerates;
int best_samplerate = 0;
if (!p) {
return 44100;
}
while (*p) {
// 找到最接近44100的采样率
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) {
best_samplerate = *p;
}
p++;
}
return best_samplerate;
}
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) {
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt) {
return 1;
}
p++;
}
av_log(nullptr, AV_LOG_ERROR, "sample format %s not support\n", av_get_sample_fmt_name(sample_fmt));
return 0;
}
static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out) {
int ret = -1;
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");
goto _END;
}
while (ret >= 0) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
return -1;
}
fwrite(pkt->data, pkt->size, 1, out);
av_packet_unref(pkt);
}
_END:
return 0;
}
/**
* 参数1:输出文件名
* 参数2:编码器名称,比如 libfdk-aac 暂时去掉
*/
int main(int argc, char* argv[]) {
av_log_set_level(AV_LOG_DEBUG);
string dst;
// string codec_name;
int ret = -1;
const AVCodec *codec = nullptr;
AVCodecContext *codec_ctx = nullptr;
FILE *fp = nullptr;
AVFrame *frame = nullptr;
AVPacket *pkt = nullptr;
uint16_t *samples = nullptr;
float t = 0;
float tincr = 0;
AVChannelLayout stereo_layout;
// 1. 输入参数
if (argc < 3) {
av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");
goto _ERROR;
}
dst = argv[1];
// codec_name = argv[2];
// 2. 查找编码器
// codec = avcodec_find_encoder_by_name(codec_name.c_str());
codec = avcodec_find_encoder_by_name("libfdk_aac"); // MAC默认不带这个,需要自己编译, 支持
// codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!codec) {
av_log(nullptr, AV_LOG_ERROR, "could not find encoder aac\n");
goto _ERROR;
}
// 3. 创建编码器上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");
goto _ERROR;
}
// 4. 设置编码器参数
codec_ctx->bit_rate = 64000;
codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;
if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) {
av_log(nullptr, AV_LOG_ERROR, "encoder does not support sample format %s\n", av_get_sample_fmt_name(codec_ctx->sample_fmt));
goto _ERROR;
}
codec_ctx->sample_rate = select_best_samplerate(codec);
av_channel_layout_copy(&codec_ctx->ch_layout, &stereo_layout);
// 设置264编码器的特殊参数
if (codec->id == AV_CODEC_ID_H264) {
// priv_data是编码器私有数据
// 通过它可以设置编码器私有参数
// 这些参数在不同编码器中是不一样的
av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);
}
// 5. 编码器与编码器上下文绑定到一起
ret = avcodec_open2(codec_ctx, codec, nullptr);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "could not open codec aac\n");
goto _ERROR;
}
// 6. 创建输出文件
fp = fopen(dst.c_str(), "wb+");
if (!fp) {
av_log(nullptr, AV_LOG_ERROR, "could not open file %s\n", dst.c_str());
goto _ERROR;
}
// 7. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");
goto _ERROR;
}
frame->nb_samples = codec_ctx->frame_size; // 每个通道的采样点数
frame->format = codec_ctx->sample_fmt;
av_channel_layout_copy(&frame->ch_layout, &codec_ctx->ch_layout);
// 真正的数据分配,并与AVFrame绑定,后面设置为0,会自动根据cpu对齐
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate frame data\n");
goto _ERROR;
}
// 8. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");
goto _ERROR;
}
tincr = 2 * M_PI * 440.0 / codec_ctx->sample_rate; // 440hz
// 9. 生成音频内容
for (int i = 0; i < 200; i++) { // 200 * 1024 / 44100 = 4.6s
// 设置数据
ret = av_frame_make_writable(frame);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "frame not writable\n");
goto _ERROR;
}
samples = (uint16_t*)frame->data[0];
for (int j = 0; j < codec_ctx->frame_size; j++) {
samples[2*j] = (int)sin(t) * 10000;
for (int k = 1; k < codec_ctx->ch_layout.nb_channels; k++) {
samples[2*j + k] = samples[2*j];
}
t += tincr;
}
// 编码
ret = encode(codec_ctx, frame, pkt, fp);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "encode error\n");
goto _ERROR;
}
}
// 10. 编码 输入空帧目的是输出队列中剩余的数据
encode(codec_ctx, nullptr, pkt, fp);
_ERROR:
if (fp) {
fclose(fp);
fp = nullptr;
}
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
if (frame) {
av_frame_free(&frame);
frame = nullptr;
}
if (pkt) {
av_packet_free(&pkt);
pkt = nullptr;
}
return 0;
}
生成图片
解码视频并保存为PGM或BMP图
其中PGM是只有灰度;
而BMP是彩色的,但确实四位对齐的,所以需要做一些额外操作:
- 需要内存对齐#pragma pack(push, 1):保证写入的结构体的大小和规范中一致,而不被cpp内存影响
- 又由于BMP是四字节对齐,不能直接把帧数据全部写入,而是一行一行写入,遇到一行数据不是4的倍数的,需要补0来padding
cpp
#include <iostream>
using namespace std;
extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t
#pragma pack(push, 1) // 强制1字节对齐
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
static int savePic(unsigned char *buf, int linesize, int width, int height, string fileName) {
FILE *fp = fopen(fileName.c_str(), "wb+");
if (!fp) {
av_log(nullptr, AV_LOG_ERROR, "could not open %s\n", fileName.c_str());
return -1;
}
// 写入头部信息,固定值,这个是PGM格式的magic头
fprintf(fp, "P5\n%d %d\n255\n", width, height);
for (int i = 0; i < height; i++) {
fwrite(buf + i * linesize, 1, width, fp);
}
fclose(fp);
return 0;
}
static void saveBMP(SwsContext *sws_ctx, AVFrame *frame, int w, int h, string fileName) {
int dataSize = w * h * 3;
FILE *fp = nullptr;
// 1. 先进行转换,将YUV frame转成BGR24 frame
AVFrame *frameBGR = av_frame_alloc();
frameBGR->width = w;
frameBGR->height = h;
frameBGR->format = AV_PIX_FMT_BGR24;
av_frame_get_buffer(frameBGR, 0);
sws_scale(sws_ctx, frame->data, frame->linesize, 0,
frame->height, frameBGR->data, frameBGR->linesize);
// BMP规范:每行字节数必须 4 字节对齐
int rowSize = (w * 3 + 3) & ~3;
int imageSize = rowSize * h;
// 2. 构造BITMAPINFOHEADER
BITMAPINFOHEADER infoHeader;
infoHeader.biSize = sizeof(BITMAPINFOHEADER);
infoHeader.biWidth = w;
infoHeader.biHeight = h * (-1);
infoHeader.biBitCount = 24;
infoHeader.biCompression = 0;
infoHeader.biClrImportant = 0;
infoHeader.biClrUsed = 0;
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biPlanes = 1;
// 3. 构造BITMAPFILEHEADER
BITMAPFILEHEADER fileHeader = {0,};
fileHeader.bfType = 0x4d42; // "BM"
fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
// 4. 将数据写入
fp = fopen(fileName.c_str(), "wb");
fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp);
fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fp);
// 每行写入,注意 frameBGR->linesize[0] 可能大于 w*3
uint8_t *srcData = frameBGR->data[0];
for (int y = 0; y < h; y++) {
fwrite(srcData + y * frameBGR->linesize[0], 1, w * 3, fp);
// 如果 w*3 不是 4 的倍数,要补 padding
uint8_t padding[3] = {0, 0, 0};
fwrite(padding, 1, rowSize - w * 3, fp);
}
// fwrite(frameBGR->data[0], 1, dataSize, fp); // 不能直接这样写,因为BMP是每行都是4位对齐的,只能逐行写入,不够四位需要补零
// 5. 释放资源
fclose(fp);
av_freep(&frameBGR->data[0]);
av_free(frameBGR);
}
static int decode(AVCodecContext *ctx, SwsContext *sws_ctx, AVFrame *frame, AVPacket *pkt, string fileName) {
int ret = -1;
ret = avcodec_send_packet(ctx, pkt);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");
goto _END;
}
while (ret >= 0) {
ret = avcodec_receive_frame(ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if (ret < 0) {
return -1;
}
string buf = "";
buf += fileName;
buf += "-";
buf += to_string(ctx->frame_num);
buf += ".bmp";
// saveBMP(sws_ctx, frame, frame->width, frame->height, buf);
savePic(frame->data[0], frame->linesize[0], frame->width, frame->height, buf);
if (pkt) {
av_packet_unref(pkt);
}
}
_END:
return 0;
}
/**
* argv[0]: 可执行程序的路径
* argv[1]: 源文件路径
* argv[2]: 目的文件路径
* */
int main(int argc, char* argv[]) {
// 1. 处理参数
string src;
string dst;
int ret = -1;
int idx = -1;
AVFormatContext *pFmtCtx = nullptr;
AVStream *inStream = nullptr;
AVPacket *pkt = nullptr;
AVFrame *frame = nullptr;
const AVCodec *codec = nullptr;
AVCodecContext *codec_ctx = nullptr;
SwsContext *sws_ctx = nullptr;
av_log_set_level(AV_LOG_INFO);
if (argc < 3) {
av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");
exit(-1);
}
src = argv[1];
dst = argv[2];
// 2. 打开多媒体文件
ret = avformat_open_input(&pFmtCtx, src.c_str(), nullptr, nullptr);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "%s\n", av_err2str(ret));
exit(-1);
}
// 3. 从多媒体文件中找到视频流
idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (idx < 0) {
av_log(pFmtCtx, AV_LOG_ERROR, "could not find audio stream in %s\n", src.c_str());
goto _ERROR;
}
inStream = pFmtCtx->streams[idx];
// 4. 查找解码器
codec = avcodec_find_decoder(inStream->codecpar->codec_id);
if (!codec) {
av_log(nullptr, AV_LOG_ERROR, "could not find encoder x264\n");
goto _ERROR;
}
// 5. 创建解码器上下文
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");
goto _ERROR;
}
// 6. 根据流参数,设置解码器上下文
avcodec_parameters_to_context(codec_ctx, inStream->codecpar);
// 7. 解码器与解码器上下文绑定到一起
ret = avcodec_open2(codec_ctx, codec, nullptr);
if (ret < 0) {
av_log(nullptr, AV_LOG_ERROR, "could not open codec x264\n");
goto _ERROR;
}
// 7.1 获得SWSContext
sws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P,
codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);
// 8. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");
goto _ERROR;
}
// 9. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");
goto _ERROR;
}
// 10. 读取视频帧,进行编码,写入目的文件
while (av_read_frame(pFmtCtx, pkt) >= 0) {
if (pkt->stream_index == idx) {
decode(codec_ctx, sws_ctx, frame, pkt, dst);
}
av_packet_unref(pkt);
}
decode(codec_ctx, sws_ctx, frame, nullptr, dst); // 这里传nullptr,表示刷新编码器
// 11. 释放资源,关闭文件
_ERROR:
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
pFmtCtx = nullptr;
}
if (codec_ctx) {
avcodec_free_context(&codec_ctx);
codec_ctx = nullptr;
}
if (frame) {
av_frame_free(&frame);
frame = nullptr;
}
if (pkt) {
av_packet_free(&pkt);
pkt = nullptr;
}
return 0;
}
#pragma pack(pop) // 恢复对齐设置