FFmpeg中比较重要的函数以及数据结构
FFMpeg 中比较重要的函数以及数据结构如下:
1. 数据结构:
(1) AVFormatContext
(2-0) AVIOContext
(2) AVOutputFormat
(3) AVInputFormat
(4) AVCodecContext
(5) AVCodec
【查找编解码器】 //遍历AVCodec链表并且获得符合条件的元素 然后返回符合的编解码器
AVCodec *avcodec_find_encoder(enum AVCodecID id) {return find_encdec(id, 1);}
AVCodec *avcodec_find_decoder(enum AVCodecID id) {return find_encdec(id, 0);}
调用同一个查找函数find_encdec() 传参0/1表示找的是编码器or解码器
//类似的:AVCodec *avcodec_find_decoder_by_name(const char *name);//根据解码器名称查找解码器并返回AVCodec
//pCodec=avcodec_find_encoder(pCodecCtx->codec_id);【根据指定解码器ID查找相应的编码器并返回AVCodec】
static AVCodec *find_encdec(enum AVCodecID id, 【编or解】int encoder)
循环会遍历AVCodec结构的【全局变量】链表,逐一比较输入的ID和每一个编码器的ID,直到找到ID取值相等的编码器
int av_codec_is_encoder(const AVCodec *codec);
int av_codec_is_encoder(const AVCodec *codec)
判定是否是编解码器 返回int
(6) AVFrame
(7) AVPicture
(8) AVPacket
(9) AVStream
(10)SwsContext视频分辩率、色彩空间变换时所需要的上下文句柄。
(11)AVCodecParser
通用
void av_register_all(void) 注册所有东西
void avcodec_register_all(void) 注册编解码器
int avformat_network_init(void); 加载socket库以及网络加密协议相关的库
int avio_open2( 创建的AVIOContext **s, 文件地址const char *url, 打开方式int flags,
const AVIOInterruptCB *int_cb, AVDictionary **options);
//≈≈≈avio_open2
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options); 使用AVCodec初始化AVCodecContext 即打开解码器
解码
初始化:
1.先进行avformat_alloc_context→→→得到一个AVFormatContext
2.int avformat_open_input(得到的AVFormatContext **ps,
打开URL:const char *filename,
指定输入格式AVInputFormat *fmt,
额外参数AVDictionary **options);
//av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL)函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中。最后三个参数用来指定文件格式,缓冲大小和格式参数,但如果把它们设置为空NULL或者0,libavformat将自动检测这些参数。
//av_open_input_file 只是检测了文件的头部
//所以接着 av_find_stream_info(pFormatCtx) 函数负责检查在文件中的流的信息。 作用是为pFormatCtx->streams填充上正确的信息。
int avformat_find_stream_info(输入的AVFormatContext *ic, 额外选项AVDictionary **options);
检查并给每个媒体流(音频/视频)的AVStream结构体赋值
/*
读取一部分视音频数据并且获得一些相关的信息
【如各媒体流对应编解码器的类型AVMediaType和ID:CodecID】
{
1.查找解码器:find_decoder() //若有解码器or类型则直接返回 若没有则调用avcodec_find_decoder(codec_id)遍历找到解码器
2.打开解码器:avcodec_open2()
3.读取完整的一帧压缩编码的数据:read_frame_internal()
//av_read_frame()内部实际上就是调用的read_frame_internal()。
4.解码一些压缩编码数据:try_decode_frame()
}
*/
找到要处理的stream[videoindex]
videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==目标枚举类型AVMEDIA_TYPE_VIDEO){videoindex=i;break;}
pCodecCtx找到并打开编解码器
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open2(pCodecCtx, pCodec,NULL) 打开解码器 即用pCodec去初始化pCodeCtx
pFrame=av_frame_alloc(); 解码得到
pFrameYUV=av_frame_alloc(); 转换成YUV格式
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
输出打印校验信息
av_dump_format(pFormatCtx,0,filepath,0);
设置并得到视频分辩率、色彩空间变换时所需要的上下文句柄
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
循环提取出一帧数据并转换:
while(av_read_frame(pFormatCtx, packet)>=0) 从AVFormatContext中读取一帧数据AVPacket
{
if(packet->stream_index==videoindex) 必须属于指定的stream
{
ret = avcodec_decode_video2(pCodecCtx, 输出pFrame, &got_picture, 输入packet); 解码一帧视频数据:AVPacket→AVFrame
if(ret < 0){printf("Decode Error.\n");return -1;}
if(got_picture){
SDL_LockYUVOverlay(bmp); 锁定
pFrameYUV->data[0]=bmp->pixels[0];
pFrameYUV->data[1]=bmp->pixels[2];
pFrameYUV->data[2]=bmp->pixels[1];
pFrameYUV->linesize[0]=bmp->pitches[0];
pFrameYUV->linesize[1]=bmp->pitches[2];
pFrameYUV->linesize[2]=bmp->pitches[1];
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
SDL_UnlockYUVOverlay(bmp);
SDL_DisplayYUVOverlay(bmp, &rect);
//Delay 40ms
SDL_Delay(40);
}
}
av_free_packet(packet);
}
FIX: Flush Frames remained in Codec 类似上面的循环 输出余下的数据
清理
SDL_Quit();
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
编码
FFmpeg源代码简单分析:avformat_write_header + av_write_frame + av_write_trailer
初始化:
int avformat_alloc_output_context2( 初始化并得到的AVFormatContext **avctx,
指定输出格式AVOutputFormat *oformat,
输出格式const char *format,
输出文件名const char *filename)
{
1)avformat_alloc_context初始化默认AVFormatContext。
2)若输入AVOutputFormat→→给予AVOutputFormat的oformat。
反之(无参数2),调用av_guess_format(使用参数34)推测输出的AVOutputFormat。然后给予AVOutputFormat的oformat。
}
编码:
int avcodec_encode_video2( AVCodecContext *avctx, 输出AVPacket *avpkt,
输入const AVFrame *frame, 编码成功标志位int *got_packet_ptr);
输出:
写视频文件头 int avformat_write_header(用于输出的AVFormatContext *s,额外选项 AVDictionary **options);
写视频数据 int av_write_frame(用于输出的AAVFormatContext *s, 等待输出的AVPacket *pkt);
写视频文件尾 int av_write_trailer(用于输出的AVFormatContext *s);
sws 1.图像色彩空间转换;2.分辨率缩放;3.前后图像滤波处理。
FFmpeg 的sws_getContext函数 、sws_scale函数 滤波器?
sws_getContext():初始化SwsContext
sws_scale():处理图像数据 转换图像格式
sws_freeContext():释放
1.初始化
struct SwsContext *sws_getContext(源&目标的宽高、像素格式+flag+输入/输出图像滤波器信息+param)
sws_getContext(w, h, YV12, w, h, NV12, 0, NULL, NULL, NULL); // YV12->NV12 色彩空间转换
sws_getContext(w, h, YV12, w/2, h/2, YV12, 0, NULL, NULL, NULL); // YV12图像缩小到原图1/4
sws_getContext(w, h, YV12, 2w, 2h, YN12, 0, NULL, NULL, NULL); // YV12图像放大到原图4倍,并转换为NV12结构
函数返回SwsContext结构体,定义了基本变换信息。
2.转换
int sws_scale( struct SwsContext *c,
源数据的通道指针const uint8_t *const srcSlice[], //Y U V分别对应一个通道指针 [R+G+B]+[R+G+B]... 故只占用一个通道 维数=1
源数据的每一行起始位置const int srcStride[],
起始位置int srcSliceY,
处理行数int srcSliceH,
输出数据的通道指针uint8_t *const dst[],
输出数据通道行字节数const int dstStride[]);
stride定义下一行的起始位置。stride和width不一定相同,这是因为:
1.由于数据帧存储的对齐,有可能会向每行后面增加一些填充字节这样 stride = width + N;
2.packet色彩空间下,每个像素几个通道数据混合在一起,例如RGB24,每个像素3字节连续存放,因此下一行的位置需要跳过3*width字节。
srcSlice和srcStride的维数相同,由srcFormat值来。
csp 维数 宽width 跨度stride 高
YUV420 3 w, w/2, w/2 s, s/2, s/2 h, h/2, h/2
YUYV 1 w, w/2, w/2 2s, 0, 0 h, h, h
NV12 2 w, w/2, w/2 s, s, 0 h, h/2
RGB24 1 w, w, w 3s, 0, 0 h, 0, 0
参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。
参数uint8_t *const dst[], const int dstStride[]定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)
3.释放sws_scale
void sws_freeContext(struct SwsContext *swsContext);