第一步:组册组件
av_register_all()
例如:编码器、解码器等等…
第二步:打开封装格式->打开文件
例如:mp4、mov、wmv文件等等
avformat_open_input();
第三步:查找视频流
如果是视频解码,那么查找视频流,如果是音频解码,那么就查找音频流
avformat_find_stream_info();
第四步:查找视频解码器
1、查找视频流索引位置
2、根据视频流索引,获取解码器上下文
3、根据解码器上下文,获得解码器ID,然后查找解码器
第五步:打开解码器
avcodec_open2();
第六步:读取视频压缩数据->循环读取
没读取一帧数据,立马解码一帧数据
第七步:视频解码->播放视频->得到视频像素数据
第八步:关闭解码器->解码完成
//第一步:组册组件
av_register_all();
//第二步:打开封装格式->打开文件
//参数一:封装格式上下文
//作用:保存整个视频信息(解码器、编码器等等)
//信息:码率、帧率等
AVFormatContext avformat_context = avformat_alloc_context();
//参数二:视频路径
const char url = [jinFilePath UTF8String]
//参数三:指定输入的格式
//参数四:设置默认参数
int avformat_open_input_result = avformat_open_input(&avformat_context, url,NULL, NULL);
if (avformat_open_input_result !=0){
NSLog("打开文件失败");
//不同的平台替换不同平台log日志
return;
}
//第三步:查找视频流->拿到视频信息
//参数一:封装格式上下文
//参数二:指定默认配置
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,NULL);
if (avformat_find_stream_info_result <0){
NSLog(" 查找失败");
return;
}
//第四步:查找视频解码器
//1、查找视频流索引位置
int av_stream_index = -1;
for (int i =0; i < avformat_context->nb_streams; ++i) {
//判断流类型:视频流、音频流、字母流等等
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
av_stream_index = i;
break;
}
}
//2、根据视频流索引,获取解码器上下文
AVCodecContext avcodec_context = avformat_context->streams[av_stream_index]->codec;
//3、根据解码器上下文,获得解码器ID,然后查找解码器
AVCodec avcodec = avcodec_find_decoder(avcodec_context->codec_id);
//第五步:打开解码器
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec,NULL);
if (avcodec_open2_result !=0){
NSLog("打开解码器失败");
return;
}
//第六步:读取视频压缩数据->循环读取
//1、分析av_read_frame参数
//参数一:封装格式上下文
//参数二:一帧压缩数据 = 一张
//av_read_frame()
//结构体大小计算:字节对齐原则
AVPacket packet = (AVPacket)av_malloc(sizeof(AVPacket));
//32 解码一帧视频压缩数据->进行解码(作用:用于解码 *** 作)
//开辟一块内存空间
AVFrame avframe_in = av_frame_alloc();
int decode_result =0;
//4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
//参数一:源文件->原始视频像素数据格式宽
//参数二:源文件->原始视频像素数据格式高
//参数三:源文件->原始视频像素数据格式类型
//参数四:目标文件->目标视频像素数据格式宽
//参数五:目标文件->目标视频像素数据格式高
//参数六:目标文件->目标视频像素数据格式类型
SwsContext swscontext = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,
NULL,
NULL,
NULL);
//创建一个yuv420视频像素数据格式缓冲区(一帧数据)
AVFrame avframe_yuv420p = av_frame_alloc();
//给缓冲区设置类型->yuv420类型
//得到YUV420P缓冲区大小
//参数一:视频像素数据格式类型->YUV420P格式
//参数二:一帧视频像素数据宽 = 视频宽
//参数三:一帧视频像素数据高 = 视频高
//参数四:字节对齐方式->默认是1
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
//开辟一块内存空间
uint8_t out_buffer = (uint8_t )av_malloc(buffer_size);
//向avframe_yuv420p->填充数据
//参数一:目标->填充数据(avframe_yuv420p)
//参数二:目标->每一行大小
//参数三:原始数据
//参数四:目标->格式类型
//参数五:宽
//参数六:高
//参数七:字节对齐方式
av_image_fill_arrays(avframe_yuv420p->data,
avframe_yuv420p->linesize,
out_buffer,
AV_PIX_FMT_YUV420P,
avcodec_context->width,
avcodec_context->height,
1);
int y_size, u_size, v_size;
//52 将yuv420p数据写入yuv文件中
//打开写入文件
const char outfile = [joutFilePath UTF8String];
FILE file_yuv420p = fopen(outfile,"wb+");
if (file_yuv420p ==NULL){
NSLog("输出文件打开失败");
return;
}
int current_index =0;
while (av_read_frame(avformat_context, packet) >=0){
//>=:读取到了
//<0:读取错误或者读取完毕
//2、是否是我们的视频流
if (packet->stream_index == av_stream_index){
//第七步:解码
//学习一下C基础,结构体
//3、解码一帧压缩数据->得到视频像素数据->yuv格式
//采用新的API
//31 发送一帧视频压缩数据
avcodec_send_packet(avcodec_context, packet);
//32 解码一帧视频压缩数据->进行解码(作用:用于解码 *** 作)
decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
if (decode_result ==0){
//解码成功
//4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
//视频像素数据格式很多种类型: yuv420P、yuv422p、yuv444p等等
//保证:我的解码后的视频像素数据格式统一为yuv420P->通用的格式
//进行类型转换: 将解码出来的视频像素点数据格式->统一转类型为yuv420P
//sws_scale作用:进行类型转换的
//参数一:视频像素数据格式上下文
//参数二:原来的视频像素数据格式->输入数据
//参数三:原来的视频像素数据格式->输入画面每一行大小
//参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)
//参数五:原来的视频像素数据格式->输入数据行数
//参数六:转换类型后视频像素数据格式->输出数据
//参数七:转换类型后视频像素数据格式->输出画面每一行大小
sws_scale(swscontext,
(const uint8_t const )avframe_in->data,
avframe_in->linesize,
0,
avcodec_context->height,
avframe_yuv420p->data,
avframe_yuv420p->linesize);
//方式一:直接显示视频上面去
//方式二:写入yuv文件格式
//5、将yuv420p数据写入yuv文件中
//51 计算YUV大小
//分析一下原理
//Y表示:亮度
//UV表示:色度
//有规律
//YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)
//YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V)
y_size = avcodec_context->width avcodec_context->height;
u_size = y_size /4;
v_size = y_size /4;
//52 写入yuv文件
//首先->Y数据
fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
//其次->U数据
fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
//再其次->V数据
fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
current_index++;
NSLog("当前解码第%d帧", current_index);
}
}
}
//第八步:释放内存资源,关闭解码器
av_packet_free(&packet);
fclose(file_yuv420p);
av_frame_free(&avframe_in);
av_frame_free(&avframe_yuv420p);
free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
不做任何预处理合并,容易导致问题
两个10s的(不同分辨率、帧率)合并之后,出现时长39s,严重超长
且出现问题:第一个视频的10s结束之后画面不动,隔了nsecond出现下一个10s的视频。
最终分析问题是,在用CLI的时候,应该注意和Cpp的API一样注意timebase,fps等配置
最终解决:
参考stackoverflow: >
FFmpeg名称中的mpeg来自视频编码标准MPEG,而前缀FF是Fast Forward的首字母缩写。
目录
默认的编译会生成 4 个可执行文件和 8 个静态库。可执行文件包括用于 转码 、 推流 、Dump媒体文件的 ffmpeg 、用于播放媒体文件的 ffplay 、 用于获取媒体文件信息的 ffprobe ,以及作为简单流媒体服务器的 ffserver 。
8个静态库其实就是FFmpeg的8个模块,具体包括如下内容。
比如AAC编码,常见的有两种封装格式
AAC 的 bit stream filter 常常应用在 编码 的过程中。
与音频的AAC编码格式相对应的是视频中的 H264编码 ,它也有两种封装格式
FFmpeg中也提供了对应的 bit stream filter ,称 H264_mp4toannexb ,可以将MP4封装格式的H264数据包转换为annexb封装格式的H264数据 (其实就是裸的H264的数据)包。
H264 的 bit stream filter 常常应用于视频解码过程中。
ffmpeg 是进行媒体文件转码的命令行工具
ffprobe 是用于查看媒体 文件头信息的工具
ffplay 则是用于播放媒体文件的工具
1首先用ffprobe查看一个音频的文件
2输出格式信息format_name、时间长度duration、文件 大小size、比特率bit_rate、流的数目nb_streams等。
3以JSON格式的形式输出具体每一个流 最详细 的信息
4显示帧信息的命令如下:
5查看包信息的命令如下:
ffplay是以FFmpeg框架为基础,外加渲染音视频 的库libSDL来构建的媒体文件播放器。
业界内开源的 ijkPlayer 其实就是基于 ffplay 进行改造的播放器,当然其做了硬件解码以及很多兼容性的工作。
在 ffplay中音画同步的实现方式其实有三种。分别是
并且在 ffplay 中默认的对齐方式也是以 音频 为基准进行对齐的。
首先要声明的是,播放器接收到的视频帧或者音频帧,内部都会有 时间戳(PTS时钟) 来标识它实际应该在什么时刻进行展示。
实际的对齐策略如下:比较视频当前的播放时间和音频当前的播放时间
关键就在于音视频时间的比较以及延迟的计算,当然在比较的过程中会设 置一个 阈值(Threshold) ,若超过预设的阈值就应该做调整(丢帧渲染 或者重复渲染),这就是整个对齐策略。
ffmpeg 就是强大的媒体文件转换工具。它可以转换任何格式的媒体文件,并且还可以用自己的 AudioFilter 以及 VideoFilter 进行处理和编辑。
接下来介绍一个解码的实例,该实例实现的功能非常单一,就是把一个视频文件解码成单独的音频PCM文件和视频YUV文件。
AVFormatContext是API层直接接触到的结构体,它会进行格式的封 装与解封装。
该结构体包含的就是与实际的 编解码 有关的部分。
331 av_register_all
所以该函数的内部实现会先调用 avcodec_register_all 来注册所有configh里面开放的编解码器,然后会注册所有的 Muxer 和 Demuxer (也就是封装格式),最后注册所有的 Protocol (即协议层的东西)。
332 av_find_codec
这里面其实包含了两部分的内容:一部分是寻找 解码器 ,一部分是寻找 编码器 。
333 avcodec_open2
该函数是打开编解码器(Codec)的函数,无论是编码过程还是解码过程,都会用到该函数。
avformat_open_input
根据所提供的文件路径判断文件的格 式,其实就是通过这一步来决定使用的到底是哪一个 Demuxer 。
avformat_find_stream_info
该方法的作用就是把所有 Stream 的 MetaData 信息填充好。
av_read_frame
使用该方法读取出来的数据是 AVPacket 。
对于 音频流 ,一个 AVPacket 可能包含 多 个 AVFrame ,但是对于 视频流 ,一个 AVPacket 只包含 一 个 AVFrame ,该函数最终只会返回一个 AVPacket 结构体。
avcodec_decode
该方法包含了两部分内容:一部分是 解码视频 ,一部分是 解码音频 , 解码 是会委托给对应的解码器来实施的。
avformat_close_input
该函数负责释放对应的资源。
avformat_alloc_output_context2
该函数内部需要调用方法avformat_alloc_context来分配一个 AVFormatContext 结构体。
avio_open2
编码的阶段了,开发者需要将手动封装好的 AVFrame 结构体,作为 avcodec_encode_video 方法的输入,将其编码成为 AVPacket ,然后调用 av_write_frame 方法输出到媒体文件中。
本文参考 音视频开发进阶指南
项目源码地址 - FFmpegDecoder
ffmpeg基本理解
整体可划分为协议层、容器层、编码层与原始数据层四个层次:
协议层:提供网络协议收发功能,可以接收或推送含封装格式的媒体流。协议层由 libavformat 库及第三方库(如 librtmp)提供支持。
容器层:处理各种封装格式。容器层由 libavformat 库提供支持。
编码层:处理音视频编码及解码。编码层由各种丰富的编解码器(libavcodec 库及第三方编解码库(如 libx264))提供支持。
原始数据层:处理未编码的原始音视频帧。原始数据层由各种丰富的音视频滤镜(libavfilter 库)提供支持
这遍文章目针对对ffmpeg基本结构和变量概念有一定了解后,想进一步理清楚个模块之间是如何关联起来,给出一个
清晰具体的流程。
播放器调用通过几个函数将这个流程串联起来,后续一一展开。
FFMPEG的输入对象AVFormatContext的pb字段指向一个AVIOContext。这是一个带有缓存的读写io上层
说明:
AVIOContext对象是一个带有缓存IO读写层。
AVIOContext的opaque实际指向一个URLContext对象,这个对象封装了协议对象及协议 *** 作对象,其中prot指向具体的协议 *** 作对象,priv_data指向具体的协议对象。
URLProtocol为协议 *** 作对象,针对每种协议,会有一个这样的对象,每个协议 *** 作对象和一个协议对象关联,比如,文件 *** 作对象为ff_file_protocol,它关联的结构体是FileContext
aviobufc函数中 ffio_fdopen()很重要,分配avio资源并建立对象,将AVIOContext和URLContext关联起来。internal->h = h;
ffio_open_whitelist = ffurl_open_whitelist +ffio_fdopen
至此,IO相关部分构造完成啦。
构造FFMPEG的输入对象AVFormatContext的iformat字段指向的对象诸如:
s→iformat 该输入流的Demuxer 存放位置。比如AVInputFormat ff_hls_demuxer
s→priv_data 这个变量很重要:存放对应的AVInputFormat *** 作的上下文信息: 比如hls中的HLSContext
构造好dexuer之后会调用 read_header2() 这个函数开启具体demuxer具体协议解析,hls开始解析:hls_read_header --->parse_playlist→
关于hls协议处理
循环构造AVFormatContext ,AVIOContext变量等。
首先看下 数据结构
然后看下,如何从在hls中 Open the demuxer for each playlist ,此时已经解析完m3u8。继续下面又干什么啦
继续分析hlsc文件获得m3u8解析额ts文件程序做了什么。
其实AVFormatContext s = pls→parent 此时作用,用的黑白名单和option设置参数,这个函数主要是还是构造访问ts文件的AVIOContext对象用的。
下图是hlsc中解析ts流流程如下:
>
以上就是关于FFmpeg的视频解码详解全部的内容,包括:FFmpeg的视频解码详解、[FFMPEG-CLI]合并不同分辨率/帧率视频注意事项-出现合并后时长问题、第三章 FFmpeg的介绍与使用等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)