前言
在学习ffmpeg中, 视频解码是一个常用而基础的功能, 本文结合自己的学习和理解, 实现一个简单的解码器。
流程图
若干说明:
- 其实第一步, avformat_alloc_context 是可以不要的, 因为在open_input 的时候, 如果检测到还没有分配。
- 会自动分配的, 所以读者无需多虑, 在这里写出来只是为了初学者知道有这么回事 经过sws_scale得到的frame是经过了变换的frame, 就可以送到显卡来显示了。
- sws_scale这一分支, 可以干的事情很多,分辨率转化,采样格式转化之类的都可以搞定。
代码实现
#include <iostream>
#ifdef __cplusplus
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}
#else
#include <libavcodec/avcodec.h>
#include <libavfilter/avfilter.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/log.h>
#include <libavdevice/avdevice.h>
#include <libswresample/swresample.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#endif
#define VEDIO_FILE1 "D:\\ZLJ\\stuff\\Vedio\\111.yuv"
#define VEDIO_FILE "D:\\ZLJ\\stuff\\Vedio\\111.mkv"
#undef main
using namespace std;
int main()
{
AVFormatContext *pFormatCtx = nullptr;
AVCodecContext *pCodecCtx = nullptr;
AVCodec *pCodec = nullptr;
AVCodecParameters *pCodecParm = nullptr;
AVPacket *pPacket = nullptr;
AVFrame *pFrame = nullptr;
AVFrame *pFrameYuv = nullptr;
SwsContext *pSwsCtx = nullptr;
size_t yuvSize;
int rst = 0, vedioIndex = -1;
int windowW, windowH;
int quit = 1;
unsigned char *outBuffer = nullptr;
FILE *file = fopen(VEDIO_FILE1, "wb+");
av_log_set_level(AV_LOG_INFO);
rst = avformat_open_input(&pFormatCtx, VEDIO_FILE, nullptr, nullptr);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "open input failed\n");
goto _EIXT2;
}
rst = avformat_find_stream_info(pFormatCtx, nullptr);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "find stream info failed\n");
goto _EIXT2;
}
rst = vedioIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "find best stream failed\n");
goto _EIXT2;
}
else {
av_log(nullptr, AV_LOG_INFO, "the vedioIndex = %d\n", vedioIndex);
}
pCodecParm = pFormatCtx->streams[vedioIndex]->codecpar;
pCodec = avcodec_find_decoder(pCodecParm->codec_id);
if (pCodec == nullptr) {
av_log(nullptr, AV_LOG_ERROR, "find decoder failed \n");
goto _EIXT2;
}
else {
av_log(nullptr, AV_LOG_INFO, "width = %d, height = %d\n", pCodecParm->width, pCodecParm->height);
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (pCodecCtx == nullptr) {
av_log(nullptr, AV_LOG_ERROR, "alloc avcodec failed \n");
goto _EIXT2;
}
rst = avcodec_parameters_to_context(pCodecCtx, pCodecParm);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "avcodec_parameters_to_context failed\n");
goto _EIXT3;
}
else {
av_log(nullptr, AV_LOG_INFO, "from codecCtx width = %d, height = %d\n", pCodecCtx->width, pCodecCtx->height);
}
rst = avcodec_open2(pCodecCtx, pCodec, nullptr);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "avcodec_open2 failed\n");
goto _EIXT4;
}
pPacket = av_packet_alloc();
if (pPacket == nullptr) {
av_log(nullptr, AV_LOG_ERROR, "av_packet_alloc failed\n");
goto _EIXT5;
}
av_init_packet(pPacket);
pFrame = av_frame_alloc();
pFrameYuv = av_frame_alloc();
windowH = pCodecParm->height;
windowW = pCodecParm->width;
windowH = (windowH >> 4) << 4;
windowW = (windowW >> 4) << 4;
outBuffer = static_cast<unsigned char *>(av_malloc(static_cast<size_t>(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, windowW, windowH, 1))));
if (outBuffer == nullptr) {
av_log(nullptr, AV_LOG_ERROR, "malloc out buff failed\n");
goto _EIXT5;
}
else {
av_log(nullptr, AV_LOG_INFO, "malloc out buff for yuv frame successfully\n");
}
rst = av_image_fill_arrays(pFrameYuv->data, pFrameYuv->linesize, outBuffer, AV_PIX_FMT_YUV420P, windowW, windowH, 1);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "fill array failed\n");
goto _EIXT6;
}
else {
av_log(nullptr, AV_LOG_INFO, "av_image_fill_array successfully\n");
}
pSwsCtx = sws_alloc_context();
rst = sws_init_context(pSwsCtx, nullptr, nullptr);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "init sws_context failed \n");
goto _EIXT6;
}
pSwsCtx = sws_getContext(pCodecParm->width, pCodecParm->height, pCodecCtx->pix_fmt,
windowW, windowH, AV_PIX_FMT_YUV420P, SWS_BICUBIC, nullptr, nullptr, nullptr);
if (pSwsCtx == nullptr) {
av_log(nullptr, AV_LOG_ERROR, "get sws context failed\n");
goto _EXIT7;
}
else {
av_log(nullptr, AV_LOG_INFO, "sws_getContext implement successfully\n");
}
yuvSize = static_cast<size_t>(windowH * windowW);
while (quit) {
while (1) {
if (av_read_frame(pFormatCtx, pPacket) < 0) {
quit = 0;
break;
}
if (pPacket->stream_index == vedioIndex) {
rst = avcodec_send_packet(pCodecCtx, pPacket);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "send packet faild\n");
continue;
} else {
break;
}
}
}
while (rst >= 0) {
rst = avcodec_receive_frame(pCodecCtx, pFrame);
if (rst == AVERROR(EAGAIN) || rst == AVERROR_EOF)
break;
if (rst == 0) {
rst = sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pFrame->height, pFrameYuv->data, pFrameYuv->linesize);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "sws_scale implement failed\n");
break;
}
fwrite(pFrameYuv->data[0], 1, yuvSize, file);
fwrite(pFrameYuv->data[1], 1, yuvSize/4, file);
fwrite(pFrameYuv->data[2], 1, yuvSize/4, file);
}
}
av_packet_unref(pPacket);
}
//flush decoder
{
rst = avcodec_send_packet(pCodecCtx, nullptr);
while (rst >= 0) {
rst = avcodec_receive_frame(pCodecCtx, pFrame);
if (rst == AVERROR(EAGAIN) || rst == AVERROR_EOF)
break;
if (rst == 0) {
rst = sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0, pFrame->height, pFrameYuv->data, pFrameYuv->linesize);
if (rst < 0) {
av_log(nullptr, AV_LOG_ERROR, "sws_scale implement failed\n");
break;
}
fwrite(pFrameYuv->data[0], 1, yuvSize, file);
fwrite(pFrameYuv->data[1], 1, yuvSize/4, file);
fwrite(pFrameYuv->data[2], 1, yuvSize/4, file);
}
}
}
fclose(file);
avcodec_send_packet(pCodecCtx, pPacket);
rst = avcodec_receive_frame(pCodecCtx, pFrame);
_EXIT7:
sws_freeContext(pSwsCtx);
_EIXT6:
av_free(outBuffer);
_EIXT5:
avcodec_close(pCodecCtx);
_EIXT4:
avcodec_parameters_free(&pCodecParm);
_EIXT3:
avcodec_free_context(&pCodecCtx);
_EIXT2:
avformat_close_input(&pFormatCtx);
return 0;
}