解码步骤
- 查找解码器 (avcodec_find_decoder)
- 打开解码器 (avcodec_open2)
- 解码 (avcodec_decode_video2)
1.视频编码
编码的详细步骤:
- 1.输入参数
- 2.查找编码器
- 3.创建编码器上下文
- 4.设置编码器参数
- 5.编码器与编码器上下文绑定到一起
- 6.创建输出文件
- 7.创建AVFrame
- 8.创建AVPacket
- 9.生成视频内容
- 10.编码
c
// 1.输入参数
if(argc < 3) {
av_log(NULL, AV_LOG_ERROR, "arguments must be more than 3\n");
goto _ERROR;
}
dst = argv[1];
codecName = argv[2];
// 2.查找编码器
codec = avcodec_find_encoder_by_name(codecName);
if(!codec) {
av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s\n", codecName);
goto _ERROR;
}
c
// 3.创建编码器上下文
ctx = avcodec_alloc_context3(codec);
if(!ctx) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
// 4.设置编码器参数
ctx->width = 640;
ctx->height = 480;
ctx->bit_rate = 500000;
ctx->time_base = (AVRational){1, 25};
ctx->framerate = (AVRational){25, 1};
ctx->gop_size = 10;
ctx->max_b_frames = 1;
ctx->pix_fmt = AV_PIX_FMT_YUV420P;
if(codec->id == AV_CODEC_ID_H264) {
av_opt_set(ctx->priv_data, "preset", "slow", 0);
}
c
// 5.编码器与编码器上下文绑定到一起
ret = avcodec_open2(ctx, codec, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Dont't open codec: %s\n", av_err2str(ret));
goto _ERROR;
}
// 6.创建输出文件
f = fopen(dst, "wb");
if(!f) {
av_log(NULL, AV_LOG_ERROR, "Dont't open file: %s\n", dst);
goto _ERROR;
}
// 7.创建AVFrame
frame = av_frame_alloc();
if(!frame) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
frame->width = ctx->width;
frame->height = ctx->height;
frame->format = ctx->pix_fmt;
ret = av_frame_get_buffer(frame, 0);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Dont't get buffer: %s\n", av_err2str(ret));
goto _ERROR;
}
// 8.创建AVPacket
pkt = av_packet_alloc();
if(!pkt) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
c
// 9.生成视频内容
for(int i = 0; i < 25; i++) {
ret = av_frame_make_writable(frame);
if(ret < 0) break;
// Y分量
for(int y = 0; y < ctx->height; y++) {
for(int x = 0; x < ctx->width; x++) {
frame->data[0][y*frame->linesize[0] + x] = x + y + i * 3;
}
}
// UV分量
for(int y = 0; y < ctx->height; y++) {
for(int x = 0; x < ctx->width; x++) {
frame->data[1][y*frame->linesize[1] + x] = 128 + y + i * 2;
frame->data[2][y*frame->linesize[2] + x] = 64 + x + i * 5;
}
}
frame->pts = i;
// 10.编码
ret = encode(ctx, frame, pkt, f);
if(ret == -1) {
goto _ERROR;
}
}
// 10.编码
encode(ctx, NULL, pkt, f);
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(NULL, AV_LOG_ERROR, "Failed to send\n");
goto _END;
}
while(ret >= 0) {
ret = avcodec_receive_frame(ctx, pkt);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
} else if(ret < 0) {
return -1;
}
fwrite(pkt->data, 1, pkt->size, out);
av_packet_unref(pkt);
}
_END:
return 0;
}
这里需要注意:
- 一定要在外面再执行一次 encode 函数,并把第二个参数置为 NULL,因为有可能缓冲区中还有剩余的数据,如果不执行可能会产生花屏等现象。
- 在使用av_frame_get_buffer函数分配data的空间之前,一定要给frame分配宽、 高、 格式,只有有了这些参数,才能正确的分配空间
2.音频编码
与视频编码类似,主要修改的地方如下:
- 输入参数的个数
- 分配data空间之前,frame参数的设置
- 生成音频的内容
c
// 1.输入参数
if(argc < 2) {
av_log(NULL, AV_LOG_ERROR, "arguments must be more than 2\n");
goto _ERROR;
}
dst = argv[1];
c
// 7.创建AVFrame
frame = av_frame_alloc();
if(!frame) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
frame->nb_samples = ctx->frame_size;
frame->format = ctx->sample_fmt;
av_channel_layout_copy(&frame->ch_layout, &ctx->ch_layout);
ret = av_frame_get_buffer(frame, 0);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Dont't get buffer: %s\n", av_err2str(ret));
goto _ERROR;
}
c
// 9.生成音频内容
float t = 0;
float tincr = 2 * M_PI * 440 / ctx->sample_rate;
for(int i = 0; i < 200; i++) {
ret = av_frame_make_writable(frame);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate space\n");
goto _ERROR;
}
samples = (uint16_t *)frame->data[0];
for(int j = 0; j < ctx->frame_size; j++) {
samples[2*j] = (int)(sin(t) * 10000);
for(int k = 1; k < ctx->ch_layout.nb_channels; k++) {
samples[2*j+k] = samples[2*j];
}
t += tincr;
}
encode(ctx, frame, pkt, f);
}
相关的2个函数:
c
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) {
const enum AVSampleFormat *p = codec->sample_fmt;
while(*p != AV_SAMPLE_FMT_NONE) {
if(*p == sample_fmt) {
return 1;
}
*p++;
}
return 0;
}
static int select_sample_rate(const AVCodec *codec)
{
const int *p;
int best_samplerate = 0;
if (!codec->supported_samplerates)
return 44100;
p = codec->supported_samplerates;
while (*p) {
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
best_samplerate = *p;
p++;
}
return best_samplerate;
}
3.生成图片
实际上是把提取视频文件和视频编码进行融合和修改,是视频编码的逆向操作,也就是视频解码。
将视频解码成一帧一帧的图片。
核心代码:
c
static void savePic(unsigned char *buf, int linesize, int width, int height, char* name) {
FILE *f;
f = fopen(name, "wb");
fprintf(f, "P5\n%d %d\n%d\n", width, height, 255);
for(int i = 0; i < height; i++) {
fwrite(buf + i * linesize, 1, width, f);
}
fclose(f);
}
static int dencode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, const char *fileName) {
int ret = -1;
char buf[1024];
ret = avcodec_send_packet(ctx, pkt);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to send dencode\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;
}
snprintf(buf, sizeof(buf), "%s-%d", fileName, ctx->frame_number);
savePic(frame->data[0],
frame->linesize[0],
frame->width,
frame->height,
buf);
if(pkt) {
av_packet_unref(pkt);
}
}
_END:
return 0;
}
c
// 2.打开多媒体文件
ret = avformat_open_input(&pFmtCtx, src, NULL, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
exit(-1);
}
// 3.从多媒体文件中找到视频流
idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if(idx < 0) {
av_log(pFmtCtx, AV_LOG_ERROR, "Does not include VIDEO\n");
goto _ERROR;
}
inStream = pFmtCtx->streams[idx];
// 4.查找解码器
codec = avcodec_find_encoder_by_name(inStream->codecpar->codec->id);
if(!codec) {
av_log(NULL, AV_LOG_ERROR, "Could not find Codec\n");
goto _ERROR;
}
// 5.创建解码器上下文
ctx = avcodec_alloc_context3(codec);
if(!ctx) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
avcodec_parameters_to_context(ctx, inStream->codecpar);
// 6.解码器与解码器上下文绑定到一起
ret = avcodec_open2(ctx, codec, NULL);
if(ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Dont't open codec: %s\n", av_err2str(ret));
goto _ERROR;
}
// 7.创建AVFrame
frame = av_frame_alloc();
if(!frame) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
// 8.创建AVPacket
pkt = av_packet_alloc();
if(!pkt) {
av_log(NULL, AV_LOG_ERROR, "No Memory\n");
goto _ERROR;
}
// 9.从源多媒体文件中读到视频数据到目的文件中
while(av_read_frame(pFmtCtx, pkt) >= 0) {
if(pkt->stream_index == idx) {
decode(ctx, frame, pkt, dst);
}
}
decode(ctx, frame, NULL, dst);
注意:
- 查找解码器的时候,不要直接写
codec = avcodec_find_encoder_by_name("libx264");
,而是应该先获取到输入流,然后根据流查找对应的解码器。 - 在进行绑定操作之前,也是应该将
inStream
中的参数拷贝到ctx
中 - 出现的段错误,是因为再次进行解码的时候,pkt传入的是NULL,此时如果再次释放就会出错,所以应该添加判断
4.生成彩色的BMP图片
上面第3部分生成的图片是黑白的,在这个部分,我们进一步优化,生成彩色的图片。
主要就是在进行解码的时候,传入了一个转换的上下文 swsCtx
, 把yuv转换成了 bmp仅此而已,原理很简单。接下来看具体的代码
核心代码:
c
// 6.1获得sws上下文
swsCtx = sws_getCachedContext(NULL,
ctx->width,
ctx->height,
AV_PIX_FMT_YUV420P,
// ctx->width,
// ctx->height,
640,
360,
AV_PIX_FMT_BGR24,
SWS_BICUBIC, NULL, NULL, NULL);
... ...
// 9.从源多媒体文件中读到视频数据到目的文件中
while(av_read_frame(pFmtCtx, pkt) >= 0) {
if(pkt->stream_index == idx) {
decode(ctx, swsCtx, frame, pkt, dst);
}
}
decode(ctx, swsCtx, frame, NULL, dst);
c
static void saveBMP(struct SwsContext *swsCtx, AVFrame *frame, int w, int h, char *name) {
int dataSize = w * h * 3;
// 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(swsCtx, (const uint8_t * const *)frame->data, frame->linesize, 0, frame->height, frameBGR->data, frameBGR->linesize);
// 2.构造 BITMAOINFOHEADER
BITMAPINFOHEADER header;
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = w;
header.biHeight = h*(-1);
header.biBitCount = 24;
header.biCompression = 0;
header.biSizeImage = 0;
header.biClrImportant = 0;
header.biClrUsed = 0;
header.biXPelsPerMeter = 0;
header.biYPelsPerMeter = 0;
header.biPlanes = 1;
// 3.构造 BITMAOFILEHEADER
BITMAPFILEHEADER bmpFileHeader;
bmpFileHeader.bfType = 0x4d42; //'BM';
bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+ dataSize;
bmpFileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
// 4.将数据写到文件
FILE* pf = fopen(filename, "wb");
fwrite(&bmpFileHeader, sizeof(BITMAPFILEHEADER), 1, pf);
fwrite(&header, sizeof(BITMAPINFOHEADER), 1, pf);
fwrite(pFrameRGB->data[0], 1, dataSize, pf);
fclose(pf);
// 5.释放资源
av_freep(&pFrameRGB[0]);
av_free(pFrameRGB);
fclose(pf);
}
注意:
- 在创建swsCtx上下文的时候,一定要明确转换前的格式和转换后的格式,直接使用ctx里面的内容有可能会出现错误,可以直接明确写出来
- 输出的图片格式一定要是符合标准的4:3 , 16:9等,原始数据有可能会不符合要求,在这里也是明确给出了,640 x 360
- 需要构造构造
BITMAOINFOHEADER
和BITMAOFILEHEADER
这两个头并填充