iOS开发心得iOS精选iOS Developer

ijkPlayer主流程分析

2017-07-30  本文已影响1015人  FindCrt

这是一个跨平台的播放器ijkplayer,iOS上集成看【如何快速的开发一个完整的iOS直播app】(原理篇)

为了学习ijkplayer的代码,最好的还是使用workspace来集成,关于worksapce我有一篇简单介绍iOS使用Workspace来管理多项目。这样可以点击函数名查看源码,也可以设置断点,修改源码测试等等。

主架构

直播框架.png

每个类型的数据流构建各自的packet和frame缓冲区,读取packet后根据类型分到不同的流程里。在最后显示的时候,根据pts同步音视频播放。

prepareToPlay

使用ijkPlayer只需两部:用url构建IJKFFMoviePlayerController对象,然后调用它的prepareToPlay。因为init时构建的一些东西得到使用的时候才明白它们的意义,所以先从prepareToPlay开始看。

prepareToPlay流程.png

上图是调用栈的流程,最终起关键作用的就是read_threadvideo_refresh_thread这两个函数。

上述两个方法都是在新的线程里运行的,使用了SDL_CreateThreadEx这个方法,它开启一个新的pthread,在里面运行指定的方法。

解码部分

开启数据流的流程.png

解码packet得到frame并入队frameQueue

解码packet并入队frame.png

到这,数据流开启了,packet得到了,frame得到了。read_thread的任务结束了。然后就是把frame显示出来的事了。

显示部分

视频播放

video_refresh_thread

video_refresh_thread.png

音频播放

音频和视频的逻辑不一样,视频是搭建好播放层(OpenGLES的view等)后,把一帧帧的数据 主动 推过去,而音频是开启了音频队列后,音频队列会过来跟你要数据,解码层这边是 被动 的把数据装载进去。

音频和视频的播放逻辑不同.png
audio_open

必须先说几个结构体,它们是连接界面层和显示层的纽带。

typedef struct SDL_Aout_Opaque SDL_Aout_Opaque;
typedef struct SDL_Aout SDL_Aout;
struct SDL_Aout {
    SDL_mutex *mutex;
    double     minimal_latency_seconds;

    SDL_Class       *opaque_class;
    SDL_Aout_Opaque *opaque;
    void (*free_l)(SDL_Aout *vout);
    int (*open_audio)(SDL_Aout *aout, const SDL_AudioSpec *desired, SDL_AudioSpec *obtained);
    ......
    一些操作函数,开始、暂停、结束等
};

SDL_Aout_Opaque在这(通用模块)只是typedef重定义了下,并没有实际的结构,实际定义根据各平台而不同,这也是解耦的手段之一。open_audio之后它的值才设置,在iOS里:

struct SDL_Aout_Opaque {
    IJKSDLAudioQueueController *aoutController;
};
.....
//aout_open_audio函数里
opaque->aoutController = [[IJKSDLAudioQueueController alloc] initWithAudioSpec:desired];

这个值用来存储IJKSDLAudioQueueController对象,是负责音频播放的音频队列的控制器。这样,一个重要的连接元素就被合理的隐藏起来了,对解码层来说不用关系它具体是谁,因为使用它的操作也是函数指针,实际被调用的函数平台各异,到时这个函数内部就可以使用SDL_Aout_Opaque的真身了。

typedef struct SDL_AudioSpec
{
    int freq;                   /**采样率 */
    SDL_AudioFormat format;     /**音频格式*/
    Uint8 channels;             /**声道 */
    Uint8 silence;              /**空白声音,没数据时用来播放 */
    Uint16 samples;             /**一个音频buffer的样本数,对齐成2的n次方,用来做音视频同步处理 */
    Uint16 padding;             /**< NOT USED. Necessary for some compile environments */
    Uint32 size;                /**< 一个音频buffer的大小*/
    SDL_AudioCallback callback;
    void *userdata;
} SDL_AudioSpec;
IJKSDLAudioQueueController

再说audio_open,它实际调用了SDL_Aoutopen_audio函数,在iOS里,最终到了ijksdl_aout_ios_audiounit.m文件的aout_open_audio,关键就是这句:

opaque->aoutController = [[IJKSDLAudioQueueController alloc] initWithAudioSpec:desired];

重点转到IJKSDLAudioQueueController这个类。如果对上图里audioQueue的处理逻辑理解了,就比较好办。

sdl_audio_callback填充AudioBuffer

整体逻辑是:循环获取音频的AVFrame,填充AudioBuffer。比较麻烦的两个点是:

音频Buffer装载.png

因为剩了一截,那么下一次填充就不能从完整的音频帧数据开始了。所以需要一个索引,指示AudioFrame的数据上次读到哪了,然后从这开始读。

while (len > 0) {
        //获取新的audioFrame
        if (is->audio_buf_index >= is->audio_buf_size) {
           audio_size = audio_decode_frame(ffp);
           if (audio_size < 0) {
               is->audio_buf = NULL;
               is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
           } else {
               is->audio_buf_size = audio_size;
           }
           is->audio_buf_index = 0;
        }
        
        len1 = is->audio_buf_size - is->audio_buf_index;
        if (len1 > len)
            len1 = len;
        if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
            memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
        else {
            memset(stream, 0, len1);
        }
        len -= len1;
        stream += len1;
        is->audio_buf_index += len1;
    }
audio_decode_frame获取音频帧数据

最后

把主流程和某些细节写了一遍,这个项目还有一些值得说的问题:

上一篇 下一篇

猜你喜欢

热点阅读