논리적 흐름
비디오 디코딩의 일반적인 단계는 다음과 같습니다.
- 출력 멀티미디어 파일 컨텍스트 가져오기
- 멀티미디어 파일에서 비디오 스트림 찾기
- 코덱 찾기
- 디코더 컨텍스트 만들기
- 디코더 매개변수 설정
- 개방형 코덱
- AVFrame 만들기
- AV패킷 생성
- 멀티미디어 파일에서 데이터 읽기
- 압축된 패킷을 디코더로 전송
- 디코딩된 데이터 프레임 가져오기
구체적인 논리 흐름도는 다음과 같습니다.
암호
mp4 파일의 비디오 스트림을 읽어 원본 비디오 프레임으로 디코딩하고 각 비디오 프레임을 사진으로 저장
#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
// 将视频帧数据保存为图片
static void savePic(unsigned char* buf, int linesize, int width, int height, char* name) {
FILE* f;
f = fopen(name, "wb");
// 向文件中写入一个P5格式的图像头信息,
// 该图像的宽度为width像素,高度为height像素,每个像素的最大灰度值为255。
fprintf(f, "P5\n%d %d\n%d\n", width, height, 255);
/*
* 使用for循环遍历每一行的像素数据,
其中buf是图像数据的首地址,linesize是每行数据的字节数。通过将buf的位置向后偏移i*linesize,获取当前行的像素数据,
然后使用fwrite函数将该行像素数据写入文件中。
fwrite函数中,第一个参数是要写入的数据的首地址,第二个参数是每个数据的大小,这里为1,第三个参数是要写入的数据数量,这里为图像的宽度。
因此,每次循环写入一个完整的行的像素数据。
*/
for (int i = 0; i < height; i++) {
fwrite(buf + i * linesize, 1, width, f);
}
fclose(f);
}
// 解码上下文,解码后存放的数据,解码数据包,输出文件名
static int decode(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 frame to decoder!\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; //退出程序
}
// 没有问题的话,把frame中的内容保存成一张张图片
//snprintf(buf, sizeof(buf), "%s-%d.bmp", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
snprintf(buf, sizeof(buf), "%s-%d", fileName, ctx->frame_number);// 通过这个方法为每一张图片构造一个名字
//saveBMP(swsCtx, frame, 640, 360, buf);
savePic(frame->data[0],
frame->linesize[0],
frame->width,
frame->height,
buf);
if (pkt) {
av_packet_unref(pkt);
}
}
_END:
return 0;
}
// 对多媒体文件中的视频流进行解码,生成一张张黑白图像
int decode_video() {
int ret = -1;
int idx = -1;
//1. 处理一些参数;
char* src;
char* dst;
const AVCodec* codec = NULL;
AVCodecContext* ctx = NULL;
AVFormatContext* pFmtCtx = NULL;
AVStream* inStream = NULL;
AVFrame* frame = NULL;
AVPacket* pkt = NULL;
struct SwsContext* swsCtx = NULL;
av_log_set_level(AV_LOG_DEBUG);
src = "F:\\test_data\\crop_jiuzhe_summer.mp4";
dst = "F:\\test_data\\photo\\out";
//2. 打开多媒体文件
if ((ret = avformat_open_input(&pFmtCtx, src, NULL, NULL)) < 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 audio stream!\n");
goto _ERROR;
}
inStream = pFmtCtx->streams[idx];
//4. 查找解码器 找到源文件的编码器id
codec = avcodec_find_decoder(inStream->codecpar->codec_id);
if (!codec) {
av_log(NULL, AV_LOG_ERROR, "Could not find libx264 Codec");
goto _ERROR;
}
//5. 创建解码器上下文
ctx = avcodec_alloc_context3(NULL);
if (!ctx) {
av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
goto _ERROR;
}
// 解码器的一些参数信息 可以通过源文件的来获取
ret = avcodec_parameters_to_context(ctx, inStream->codecpar);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Could not copyt codecpar to codec ctx!\n");
goto _ERROR;
}
//5. 解码器与解码器上下文绑定到一起
ret = avcodec_open2(ctx, codec, NULL);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
goto _ERROR;
}
//6. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
//7. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
//8. 从源多媒体文件中读到视频数据
while (av_read_frame(pFmtCtx, pkt) >= 0) {
if (pkt->stream_index == idx) {
decode(ctx, frame, pkt, dst);
}
}
// 对于解码器和编码器存在同样的问题,有些数据可能还没有解出来,强制让它输出
decode(ctx, frame, NULL, dst);
//9. 将申请的资源释放掉
_ERROR:
if (pFmtCtx) {
avformat_close_input(&pFmtCtx);
pFmtCtx = NULL;
}
if (ctx) {
avcodec_free_context(&ctx);
ctx = NULL;
}
if (frame) {
av_frame_free(&frame);
frame = NULL;
}
if (pkt) {
av_packet_free(&pkt);
pkt = NULL;
}
printf("hello, world!\n");
return 0;
}