FFmpeg这部分想了很久,也没找到比较好的讲解方式,本来想像其它博客一样,对着代码一行行的分析。但后来感觉不太现实,FFmpeg应用在IOS上怎么说代码最少也有个5、6k行(包括音视频、业务逻辑),再加上因为小弟也要上班养家,所以没这么多时间写的很详细,只能做一个随笔,简而化之的就整个架构描述描述。不过所有这些提到的地方都是使用的核心难点。不清楚地方还请大家多多包涵,请勿拍砖。呵呵
另外除了这篇还准备写一篇FFmpeg for ios架构:高级篇,请大家多多关注。
整个代码 分为四个部分:
(1) 音频播放模块(audio unit控制)
(2) 视频贴图模块 opengl
(3) 音视频解码模块 ffmpeg
(4) 业务逻辑 音视频同步 循环解码 模块oc swift
1 视频贴图模块
这部分依然采用的是OpenGL纹理贴图模块,这种方法使用起来和之前的录像以及视频播放界面展示的原理是一样的,这里就不再细表(可以参考博客中其它篇)。但是有一点要介绍一下,如何使用OpenGL现实RGB与YUV。
这里介绍两种方法:RGB
CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
videoTextureCache,
pixelBuffer,
NULL,
GL_TEXTURE_2D,
GL_RGBA,
frameWidth,
frameHeight,
GL_BGRA,
GL_UNSIGNED_BYTE,
0,
&texture);
因为上面取到的是ios相机视频流,用上面一种方法更好。
YUV:
glTexImage2D(GL_TEXTURE_2D,
0,
GL_LUMINANCE,
widths[i],
heights[i],
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
pixels[i]);
除了YUV我们还能显示各种参数:
/* PixelFormat */
#define GL_DEPTH_COMPONENT 0x1902
#define GL_ALPHA 0x1906
#define GL_RGB 0x1907
#define GL_RGBA 0x1908
#define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A
再来看看音频播放模块:
2 Audio Unit 音频播放模块(参见专栏中Aduio Unit博客)
2.1 对audio Session的各种属性进行监听进行监听
常用的监听:
中断监听、音频输出源监听(插拔耳机)、音量监听
常用的属性设置:
播放录音模式属性、音频硬件处理采样周期设置
举两个例子:
插拔耳机监听设置:
AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
sessionPropertyListener,
(__bridge void *)(self)
音频模式设置:
UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
sizeof(sessionCategory),
&sessionCategory),
"Couldn‘tset audio category")
2.2 设置Audio Unit各种属性(参见专栏中Aduio Unit博客)
回调方法中,主要是需要逻辑层,控制通过ffmpeg解码之后的数据一段段的装载到audio unit中。这部分逻辑比较复杂,在ffmpeg逻辑层介绍。
另外Audiounit拿到数据之后,还要判断下量化类型,是整形还是浮点。如果是16位整形,这里是要转化位浮点数来进行处理的。
3 FFmpeg解码
重点的部分来了,这个重点是如何进行解码。
3.0 注册所有的解码器 av_register_all();
3.1 视频源类型:拿到本地视频文件路径或者网络rtsp流媒体我么需要对视频源类型进行判断,如果是网络视频,那么我们需要初始化avformat_network_init();初始化网络解码的相关方法。
3.2 打开文件获取文件的相关属性
ffmpeg的基本结构体的类型描述,参见另一篇博客:
这里对每个变量或者结构体就不做详细解释了。
初始化AVFormatContext
avformat_alloc_context();
打开文件
avformat_open_input
查找视频文件的流信息
avformat_find_stream_info(formatCtx, NULL)
获取音视频流文件各种信息:
av_dump_format(formatCtx, 0, path, false);
下面就开始对音视频分别进行处理了
3.3视频流处理
根据AVMEDIA_TYPE_VIDEO 遍历stream,找到视频流轨道序号,比如这里视频轨道是0
根据视频流序号,我们可以获取解码器的上下文:
AVCodecContext *codecCtx = _formatCtx->streams[videoStream]->codec;
根据解码器的上下文寻找响应的解码器
avcodec_find_decoder(codecCtx->codec_id);
为视频帧分配空间
avcodec_alloc_frame();
无论是播放本地视频还是网络的rtsp流都有这个步骤的。
3.4音频流处理
根据AVMEDIA_TYPE_AUDIO 遍历stream,找到视频流轨道序号,比如这里视频轨道是1
根据视频流序号,我们可以获取解码器的上下文:
AVCodecContext *codecCtx = _formatCtx->streams[audioStream]->codec;
查找音频解码器
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
这里比视频多一步:如果当前设备的音频硬件不支持我们解码的这种数据类型需要调用下面这个方法转化一下:
swr_alloc_set_opts
为音频帧分配空间
AVStream *st = _formatCtx->streams[_audioStream];
3.5 ffmpeg解码逻辑
前期准备完成之后,点击播放按钮,业务逻辑层就开始调用ffmpeg方法,开始解码。
首先开始读取音视频包
av_read_frame(_formatCtx, &packet)
如果是音频数据调用音频解码模块,视频数据调用视频解码模块:
解码视频:
int len = avcodec_decode_video2(_videoCodecCtx,
_videoFrame,
&gotframe,
&packet);
解码音频:
int len = avcodec_decode_audio4(_audioCodecCtx,
_audioFrame,
&gotframe,
&packet);
如果数据buffer超出最低门限,或者packetsize剩余大小为零,那么当前解码循环都停止。因为每次读取的包的大小,可能不只包含一张图片或者一帧音频,所以每次解码之后packetsize的大小都减小了。视频解码之后可以转化为YUV,也可以使用YUV转RGB。这里ffmpeg解码之后原生的就是YUV,不需要再转化为RGB了。当然转化为RGB之后处理图片特效等更方便。
3.6 快进快退拖动
和文件的操作类似,都是seek移动即可。
avformat_seek_file(_formatCtx, _videoStream, ts, ts, ts, AVSEEK_FLAG_FRAME);
avcodec_flush_buffers(_videoCodecCtx);
4 业务逻辑部分
4.1 音频播放数据装载逻辑
4.1.1 首先清空audio io中buffer空间。比如这里每次处理512个点,双通道,16位量化位数,那么每次处理4096个字节。
4.1.2 读取音频帧中数据,拿到数据之后每次处理4096点,如果当前数据为处理完,那么等待下次audio io中buffer有空间时继续装载,直至当前音频帧中所有音频数据都处理完位置。
4.1.3 如果当前音频帧已经处理完,那么继续从音频帧中读取下一帧音频数据。重复3.1.2的步骤。因为音频控制逻辑在Audio Unit已经设置完成,所以这里我们只需要将数据即使的装载进去即可。
4.2 视频播放同步逻辑、解码逻辑
4.2.1 第一次解码之后要延迟一段时间,等待100ms左右,等待音视频将buffer最小门限装满然后在开始播放。
4.2.2 播放采用递归调用,每次视频帧展示时间,需要调用视频帧时间戳校验。通过当前系统时间戳与当前播放时间戳对比,决定当前显示帧的显示时间,可以尽可能的将每帧图片的显示时间平均。
4.2.3 同时每次显示一帧图片,那么进行一次音视频解码,看看是否音视频缓存buffer是否已经低于最低门限了,如果低于那么开始解码。
4.3 音视频同步逻辑
音视频在播放的时候有时候会出现音频过快或者过慢的情况,这时就需要对音视频播放时间进行调整。
通过3.1的音频装载逻辑我们已经知道音频的数据填充过程。所以:
4.3.1 音频过快:
从音频帧中取出数据之后,我们并没有吧当前音频数据从音频帧中移除,而是将所有的Audio IO buffer内容填充0,类似静音效果,这样就可以实现类似音频帧等待的效果。
4.3.2 音频过慢:
而音频过慢时,我们直接将当前处理音频帧从音频帧队列中移除,不做任何audio io buffer 操作,进行读取下一帧音频帧。如果下一帧音频帧还很慢,继续移除,读取第三帧音频帧。知道音频帧速度适中为止或者音频buffer为空停止。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文:http://blog.csdn.net/u014011807/article/details/46998829