ijkplayer数据读取

在IjkMediaPlayer_prepareAsync()中它创建了几个线程:

  • 视频显示线程
  • 数据读取线程
  • 消息循环处理线程

数据读取线程

数据读取线程是在stream_open()函数中创建的,把里面函数可以精简为下面几行:

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{
        frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1)
        frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1)
        packet_queue_init(&is->videoq);
        packet_queue_init(&is->audioq);
        SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout") 
        SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read")
}

前面几句都是初始化队列的操作,分为两个队列:一个视频,一个音频。frame_queue_init()的第一个参数是解码出来的队列,第二个参数是未解码的队列

read_thread()

static int read_thread(void *arg)
{

//...
   err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);//
  //...
    err = avformat_find_stream_info(ic, opts);//
//...
   /* open the streams */
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);//
    }

    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);//
    }
//...
 for (;;) {
    if (is->seek_req) {
        avformat_seek_file();
    } 
    ret = av_read_frame(ic, pkt);
    packet_queue_put(&is->audioq, pkt); 
     //如果是视频的话 
    //packet_queue_put(&is->videoq, pkt);  
    }
}

avformat_open_input()内部就是探测数据源的格式(这里会跳转到ffmpeg)。(其实在avformat_open_input()/Utils.c内读取网络数据包头信息时调用id3v2_parse(),然后获取到头信息后会执行ff_id3v2_parse_apic()/id3v2.c。最后会更改ic这个AVFormatContext型的参数?)

avformat_find_stream_info()解析流并找到相应的解码器,解码器在JNI_Load()函数里面ijkmp_global_init()的avcodec_register_all()就注册过了。

对于音频或者视频都会调用stream_component_open()函数来进行音视频读取和解码

stream_component_open()

static int stream_component_open(FFPlayer *ffp, int stream_index)
{
  //...
    codec = avcodec_find_decoder(avctx->codec_id);
    switch (avctx->codec_type) {
        case AVMEDIA_TYPE_AUDIO   : is->last_audio_stream    = stream_index; forced_codec_name = ffp->audio_codec_name; break;
        
        // FFP_MERGE: case AVMEDIA_TYPE_SUBTITLE:
        case AVMEDIA_TYPE_VIDEO   : is->last_video_stream    = stream_index; forced_codec_name = ffp->video_codec_name; break;
        default: break;
    }
//...
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
        /* prepare audio output */
        // ①
        if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
            goto fail;
      //...
        // 里面有d->queue = queue这一步是将ffplayer的audioq赋值给解码器中的queue队列,用于解码使用
        decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
      //...
        //
        if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0) 
            goto fail;
        SDL_AoutPauseAudio(ffp->aout, 0);
        break;
        
    case AVMEDIA_TYPE_VIDEO:
   //...
        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
        ffp->node_vdec = ffpipeline_open_video_decoder(ffp->pipeline, ffp);
   //...
        //
        if ((ret = decoder_start(&is->viddec, video_thread, ffp, "ff_video_dec")) < 0)
            goto fail;
       //...
        break;
    }
fail:
    av_dict_free(&opts);

    return ret;
}

type == audio

函数会调用audio_open(),它里面调用aout->open_audio(aout,pause_on),这个open_audio()也是我们之前初始化的时候设置过(func_open_audio_output(),这个函数选好输出音频的工具后就会给open_audio赋值),最终会调用aout_open_audio_n(),最后会:

SDL_CreateThreadEx(&opaque->_audio_tid, aout_thread);

aout_thread线程从解码好的音频帧队列sampq中,循环取数据推入AudioTrack中播放。这里又创建了一个线程,就叫音频输出线程吧(AudioTrack仅仅能播放已经解码的PCM流)

audio_open()中还有个需要注意的地方(后面播放流程会用到):

wanted_spec.callback = sdl_audio_callback;

接着stream_component_open()中调用了decoder_start(),这个函数里面会创建一个线程:

SDL_CreateThreadEx(&is->_audio_tid, audio_thread, ffp, "ff_audio_dec");

音频解码线程,从audioq队列中获取音频包,解码并加入sampq音频帧列表中。

type == video

也是在decoder_start()里面创建一个线程:

SDL_CreateThreadEx(&is->_video_tid, video_thread, ffp, "ff_video_dec")

视频解码线程,这个线程里分为硬解码和软解码。

  • 软解:从videoq队列中获取视频包,解码视频帧放入pictq列表中
  • 硬解:从videoq队列中获取视频包,推送MediaCodec解码,获取解码outputbuffer index并存储在pictq列表中

接着在stream_component_open()后有一个无限for循环,作用是循环读取数据,然后把数据放入相应的audio队列或者video队列中。

在for循环中,先判断是否暂停读取数据,因为有时队列满了。还会判断是否拖动了快进或者后退的操作。

在for循环的最后:

            if (is->audio_stream >= 0) {
                packet_queue_flush(&is->audioq);
                packet_queue_put(&is->audioq, &flush_pkt);
            }

            if (is->subtitle_stream >= 0) {
                packet_queue_flush(&is->subtitleq);
                packet_queue_put(&is->subtitleq, &flush_pkt);
            }

            if (is->video_stream >= 0) {
                if (ffp->node_vdec) {
                    ffpipenode_flush(ffp->node_vdec);
                }
                packet_queue_flush(&is->videoq);
                packet_queue_put(&is->videoq, &flush_pkt);
            }

循环地向几个队列中put数据,这里看起来有三个队列,分别是音频、视频、字幕。

总结

  • 在读取数据的时候,当队列满了的时候会delay,然而并不会断开连接
  • 在读取的时候,有很多操作都是受java层界面的影响,比如pause和resume操作,seek操作等。如果界面按了暂停什么的,都会反馈到这里,然后这里无限for循环的时候会相应作出各种操作。
  • 这里会不断读取音频和视频,然后放入到相应队列中
  • read_thread线程里面会创建两个解码线程,一个音频输出线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值