1.准备上下文
AVFormatContext
AVCodec AVCodecContext
2.准备上下文的原子对象
AVFrame
AVPacket
3.配置解码器
一种是直接find decoder输入AVCodecID即可
另一种是按AVFormatContext->streams[st_index_video]->codecpar->codec_id来find decoder
第二种比较保险,寻找格式方便,比如若解析txt文件,他会自动找到‘tty’格式的解码器,如果是我自己,一定很难找到的。这种方法根本上是根据后缀名判断的,若无后缀名则在format open input的时候就返回NULL了,这种情况下若自己对数据内容有把握,可以自己设定AVInputFormat,退一步说,你随便设置一个InputFormat,后面read_frame以后再用合适的方法来处理也是绝对可以的!(但此时format组件的功能函数就别用了,比如dump format之类的)
4.解码
大致没啥说的,都是send_packet然后recive_frame,或者decode_video/audio(据说这种方法将来会被放弃)。下面举个例子说明一下这种最简单粗暴的解码方法
0.原因
MJPEG的视频码流是可以直接播放的,所以解封装以后直接保存到文件即可,最简单
1.目的
保存MJPEG格式的码流到文件并播放
2.代码
环境: Qt 5.7.1 64位
#include
#include
#include
#include
#include
extern "C" {
#include
#include
#include
#include
#include
#include
}
#define STR(str) QString::fromLocal8Bit((char*)str)
#define LOG qDebug()
#define INBUF_SIZE 4096
#define DST_FILENAME QString("D:/fmt_avi/")
int main(int argc, char** argv)
{
char* filename = "D:/fmt.avi";
AVFormatContext* ic=0;
AVCodec* c=0;
AVCodecContext* cc=0;
AVPacket* pkt=0;
AVFrame* frame=0;
int ret=-1;
ic= avformat_alloc_context();
if(avformat_open_input(&ic, filename, 0, 0) <0)
exit(1);
int st_idx= -1;
for(int i=0; inb_streams; ++i)
if(ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
st_idx= i;
break;
}
if(-1 == st_idx){
avformat_close_input(&ic);
exit(1);
}
avformat_find_stream_info(ic, 0);
av_dump_format(ic, 0, "", 0);
c= avcodec_find_decoder((AVCodecID)ic->streams[st_idx]->codecpar->codec_id);
cc= avcodec_alloc_context3(c);
if(!cc){
avformat_close_input(&ic);
exit(1);
}
if(avcodec_open2(cc, c, 0) <0)
goto end;
pkt= av_packet_alloc();
frame=av_frame_alloc();
//!
FILE* fp= fopen("D:/fmt.mjpg", "wb");
//!
int got_pict= 0;
while(av_read_frame(ic, pkt) >=0){
if(pkt->stream_index == st_idx){
fwrite(pkt->data, pkt->size, 1, fp);
}
}
end:
avformat_close_input(&ic);
avcodec_free_context(&cc);
av_packet_free(&pkt);
av_frame_free(&frame);
fclose(fp);
return 0;
}
3.效果
用ffplay播放:ffplay -i D:/fmt.mjpg
很多时候解码没这么简单,下面还以上面视频文件为例,对数据进行解码到YUV420P的像素格式并播放
1.简介及准备
视频流最终有两种去路,即RGB和YUV,YUV即灰度、色度、饱和度,要注意的是a*b大小的YUV图片其UV通道大小都只有(a*b)/2,原因在这里;其次一般的播放器无法直接播放YUV文件,需要下载YUV Player,做好以上工作以后,开始写代码。
2.代码
#include
#include
#include
#include
#include
extern "C" {
#include
#include
#include
#include
#include
#include
}
#define STR(str) QString::fromLocal8Bit((char*)str)
#define LOG qDebug()
#define INBUF_SIZE 4096
#define DST_FILENAME QString("D:/fmt_avi/")
int main(int argc, char** argv)
{
char* filename = "D:/fmt.avi";
AVFormatContext* ic=0;
AVCodec* c=0;
AVCodecContext* cc=0;
AVPacket* pkt=0;
AVFrame* frame=0;
int ret=-1;
ic= avformat_alloc_context();
if(avformat_open_input(&ic, filename, 0, 0) <0)
exit(1);
int st_idx= -1;
for(int i=0; inb_streams; ++i)
if(ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){
st_idx= i;
break;
}
if(-1 == st_idx){
avformat_close_input(&ic);
exit(1);
}
avformat_find_stream_info(ic, 0);
av_dump_format(ic, 0, "", 0);
c= avcodec_find_decoder((AVCodecID)ic->streams[st_idx]->codecpar->codec_id);
cc= avcodec_alloc_context3(c);
if(!cc){
avformat_close_input(&ic);
exit(1);
}
if(avcodec_open2(cc, c, 0) <0)
goto end;
pkt= av_packet_alloc();
frame=av_frame_alloc();
AVFrame* frameYUV= av_frame_alloc();
//!
FILE* fp= fopen("D:/fmt_avi/stream.yuv", "wb");
// if(!fp)
// goto end;
//
AVCodecParameters* cpar= ic->streams[st_idx]->codecpar;
av_image_alloc(frameYUV->data, frameYUV->linesize, cpar->width, cpar->height, AV_PIX_FMT_YUV420P, 1);
SwsContext* swsc= sws_getContext(cpar->width, cpar->height, (AVPixelFormat)cpar->format,
cpar->width, cpar->height, AV_PIX_FMT_YUV420P,
SWS_FAST_BILINEAR, 0,0,0);
int got_pict= 0;
while(av_read_frame(ic, pkt) >=0){
if(pkt->stream_index == st_idx){
if(avcodec_decode_video2(cc, frame, &got_pict, pkt) >=0){
if(got_pict){
ret= sws_scale(swsc,
(const uint8_t* const*)frame->data, frame->linesize, 0, frame->height,
(uint8_t* const*)frameYUV->data, frameYUV->linesize);
if(ret != 0){
fwrite(frameYUV->data[0], cc->width*cc->height, 1, fp);
fwrite(frameYUV->data[1], cc->width*cc->height/4, 1, fp);
fwrite(frameYUV->data[2], cc->width*cc->height/4, 1, fp);
LOG << "write one frame" << cc->frame_number;
//
}
}
}
}
}
end:
avformat_close_input(&ic);
avcodec_free_context(&cc);
av_packet_free(&pkt);
av_frame_free(&frame);
av_frame_free(&frameYUV);
fclose(fp);
sws_freeContext(swsc);
av_free(&frameYUV->data[0]);
return 0;
}
首先解码,然后转图像数据
我先用av_image_alloc分配目标YUV图像,再用sws (switch scale)内的sws_getContext获取格式转换组件,然后sws_scale转换图像,最终将frameYUV写入文件。
4.参考
没有大佬们的努力,我不可能进步这么快。
此处参考了雷的文章
参考
https://blog.csdn.net/liaozc/article/details/6110474
这里介绍了普遍的data和linesize的关系,RGB中只占第一行,YUV则占三行,长度比约为2:1:1,约的是padding size
https://blog.csdn.net/mydear_11000/article/details/50404084
这里介绍了YUV420,422,444格式的区别,对于写代码需要知道的是:
YUV420P 中UV通道为width/2, height/2
YUV422P中UV通道为width/2, height
YUV444P中UV通道为width, height
https://blog.csdn.net/lanxiaziyi/article/details/74347911
这里介绍了YUVJ和YUV的普遍区别
函数简介
比较简单,略。