FFmpeg iOS 音频开发的小总结

iOS 音频开发一般是 AudioFileStream 配合 Audio Queue 或者 Audio Unit实现的,而 FFmpeg 是与原生截然不同的软解码实现,比起原生更支持了诸如 flac、ape、acc、m4a、mp3、wav等格式(原生可通过第三方支持 flac 等格式)。因为工作项目的音乐播放器是原生那套实现的,所以就想换个思路,看看用 FFmpeg 开发音频是怎样的体验。

我对 FFmpeg 的探索起初是从简单 demo 入坑,发现大部分 demo 都需要 sdl,再去了解过 sdl,可谓踩了不少坑,直到我上手 kxmovie、ijkplayer,思路才渐渐清晰起来。经过对比,ijkplayer 是基于 c 实现的项目,实现了跨平台,而 kxmovie 是偏 iOS 实现的项目;kxmovie 在播放 m3u8 有点卡顿的bug,而 ijkplayer 则没有。可知 kxmovie 无论是代码兼容性还是播放视频的效果都没有 ijkplayer 好。ijkplayer 是 Bilibili 开源的一个项目,它基于 FFmpeg 开发了支持移动端音视频解码、视频渲染、播放控制、状态监控等功能。通过 ijkplayer 源码的学习,我对 FFmpeg 有了大致了解,而本文主要聊聊音频相关的。

FFmpeg 的导入

ijkplayer ReadMe 里有关如何编译 FFmpeg 的介绍,只要注意下是否支持更多解码格式即可,将生成出的 .a 文件拖进项目里,为项目添加 libbz.tbd,libbz2.tbd ,再在Build SettingsLibrary Search Paths添加 项目中 FFmpeg 的.a文件的目录,如 $(PROJECT_DIR)/your_project_name/ffmpeg/lib,FFmpeg 的导入完成了。

代码处理流程大致介绍

我只挑了几个有意思的来总结下,像 ffplay.c 一来就两三千行代码,虽然流程都是套路,但是不记录一下的话,时间久了,挺容易没什么头绪。

负责底层调用的 ffplay.c,首先注册解码器,初始化 FFPlayer 和 VideoState 开启一条线程调用 read_thread函数。在函数中调用avformat_open_input打开多媒体文件,打开文件后avformat_find_stream_info获取文件中的流信息填充进为ic->streams,获取流信息后使用av_find_best_stream获取文件的音频和视频流,并准备对音频和视频信息进行解码。接着调用stream_component_open函数,通过avcodec_find_decoder找到codec_id已注册的音视频解码器,再就是avcodec_open2打开解码器准备音视频的解码,再从audio_open开启sdl_audio_callback回调。此时在read_thread函数中会循环读取av_read_frame(ic, pkt)包数据,并将包数据存入包队列以供解码时使用。而对于音频解码会起新的线程调用audio_thread(视频则是video_thread),取出包数据后,使用avcodec_decode_audio4将解码后的 Frame 交给帧队列。sdl_audio_callback里的audio_decode_frame负责从帧队列中取出 Frame frame_queue_peek_readable(&is->sampq),完成重采样后,将 data 通过memcpy拷贝的方式回调给高层使用。

ijkplayer.c 是对 ffplay.c 的封装,包括播放暂停,获取文件时长,可播放时长,seek到特定时间点播放等,也实现了播放器的状态的监听。

ijksdl_aout_ios_audiounitijksdl_aout的设计挺有趣,它们共同串联了从高层到 FFmpeg 层的操作,通过指针函数,在ijksdl_aout_ios_audiounit注册了高层音频调用实现,ijksdl_aout负责供 FFmpeg 调用,从而达到解藕的效果。

实践

先来张效果图

基于边学习边动手的原则,我完成了一个仅支持音频播放的 demo,因为仅仅是支持音频,demo中对 ijkplayer 的几个文件做了点修改,比如剔除原来视频相关的代码,修改其仅从音频文件中取出封面

总结

在学习和使用中还是发现了一点小遗憾,FFmpeg 还没有提供对 io 层的缓存支持,这导致了在播放网络文件的时候拖拽进度条会重新进行缓冲,也无法实现边播边存的功能,我尝试过获取pkt的data并在av_read_frame的ret<0文件结束的情况下缓存起来,但这其实并没能保证帧顺序,拖拽过进度条之后的data也将会不完整,所以放弃了这种方案,官方的说法是在 libavformat/cache.c 中进行实现。

有个小问题是对于缓冲进度的计算有点小误差,playableDuration 无法与多媒体文件时长一致。我尝试在av_read_frame文件结束的地方

之后发送一个通知,直接将缓冲进度条置为100%

还有个小问题是 ijkplayer 播放本地文件的时候,假如是一首2、30m的无损歌曲,播放器不会一下子把整个文件都解码进内存中,这时当seek到文件未进入到内存的部分,播放器就直接停止播放了,我试过修改MAX_QUEUE_SIZE的值也是无效,最后 bbcallen 回复我说:That should be an ffmpeg issue, take a look at the comment of avformat_seek_file(),如此看来也是暂时解决无望。

在 ijkplayer 的 某个issue 中看到了 bbcallen 貌似说b站的客户端也用系统原生来播放视频,我觉得iOS播放视频的话使用MPMoviePlayerController就好了。

当然,如果是直播项目,无疑是 FFmpeg 的用武之地。

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

1 2 收藏 1 评论

关于作者:Hawk0620

喜欢新奇事物,爱探索和冒险,用艺术的视角欣赏技术,也迷恋篮球和音乐。 个人主页 · 我的文章 · 2 ·  

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部