美文网首页iOS Developer
ijkplayer 源码解析2(数据读线程)

ijkplayer 源码解析2(数据读线程)

作者: pengxiaochao | 来源:发表于2023-02-06 22:58 被阅读0次

    ijkplayer源码解析系列文章

    本章主要介绍媒体文件的读取

    1.播放器初始化准备工作
    2.打开媒体Stream
    3.read_thread 读线程工作

    业务方在使用IJKFFMoviePlayerController的时候,一般的流程是这样的:

    • 初始化播放器实例,
    • 设置入拉流地址,
    • 准备播放
    • 开始播放

    播放器代码的实现一般是这样;

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        /// 初始化播放器
        self.player = [[IJKFFMoviePlayerController alloc] initWithContentURL:self.url withOptions:options];
        self.player.view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        self.player.view.frame = self.view.bounds;
        self.player.scalingMode = IJKMPMovieScalingModeAspectFit;
        self.player.shouldAutoplay = YES;
        [self.player setPauseInBackground:YES];
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        ///准备播放
        [self.player prepareToPlay];
    }
    

    1.初始化播放器相关

    播放器的初始化会调用到OC层的
    - (id)initWithContentURLString:(NSString *)aUrlString withOptions:(IJKFFOptions *)options 方法,该方法主要实现了播放器在iOS侧的一些实例对象的创建,消息队列的创建,音频Session的设置,数据源的设置 等功能;

    • 1.注册ffmpeg
    • 2.ffmpeg版本检查
    • 3.创建消息队列loop
    • 4.创建IJKSDLGLView渲染视图
    • 5.设置输出的ijkplayer log日志级别
    • 6.初始化音频AudioSession类型
    1.1 注册ffmpeg
    /// 注册编码器
    avcodec_register_all();
    //注册ffmpeg 相关复用器/解复用器、以及各种协议
    av_register_all();
    ijkav_register_all();
    // 注册网络相关协议
    avformat_network_init();
    // 初始化刷新AVPacket(用于seek等操作)
    av_init_packet(&flush_pkt);
    
    1.2 ffmpeg版本检查
    ///检查ffmpeg 版本是否匹配
    const char *actualVersion = av_version_info();
    const char *expectVersion = kIJKFFRequiredFFmpegVersion;
    if (0 == strcmp(actualVersion, expectVersion)) {
        return YES;
    } else {
        /// 省略代码...
        return NO;
    }
    
    1.3 创建消息队列loop
    ///消息池,处理播放过程中的各种消息
     _msgPool = [[IJKFFMoviePlayerMessagePool alloc] init];
    
    1.4 创建IJKSDLGLView渲染视图
    /// 渲染View的创建(基于OpenGL ES2.0)
     _glView = [[IJKSDLGLView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    1.5 设置输出的ijkplayer log日志级别
    #ifdef DEBUG
            [IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_DEBUG];
    #else
            [IJKFFMoviePlayerController setLogLevel:k_IJK_LOG_SILENT];
    #endif
    
    1.6 初始化音频AudioSession类型
    ///内部调用AVAudioSession
    [[IJKAudioKit sharedInstance] setupAudioSession];
    

    2.设置播放数据源

    初始化ijkplayer的时候传入播放地址 最终调用到 - (id)initWithContentURL:(NSURL *)aUrl withOptions:(IJKFFOptions *)options;方法,

    这里只是把播放器地址设置进了_mediaPlayer 实例中;

    /// 把播放地址设置给 IjkMediaPlayer 实例
    ijkmp_set_data_source(_mediaPlayer, [_urlString UTF8String]);
    

    3.准备播放

    [IJKFFMoviePlayerController prepareToPlay] 准备播放;

    调用堆栈如下:

    prepareToPlay
      ijkmp_prepare_async
        ijkmp_prepare_async_l
          ffp_prepare_async_l
             stream_open
    

    3.1最核心的方法是stream_open() 的内部实现, stream_open()函数 实现了如下功能:

    • 1.初始化视频packet queue音频packet queue字幕packet queue
    • 2.初始化视频Frame queue音频Frame queue字幕Frame queue
    • 3.初始化音频时钟Clock视频时钟Clock外部时钟Clock
    • 4.创建读取线程 及函数read_thread
    • 5.创建视频刷新线程 及其函数video_refresh_thread
    3.1.1.初始化视频packet queue音频packet queue字幕packet queue

    视频FrameQueue大小3 ,字幕FrameQueue大小16,AudioQueue大小9;keep_last 表示保留上一帧,这里只有视频需要保留;

    /// 初始化视频FrameQueue 并设置size大小3 
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
            goto fail;
    // /初始化字幕FrameQueue size 大小16
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    /// 初始音频FrameQueue size 大小9
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;
    
    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;
    
    3.1.2.初始化视频Frame queue音频Frame queue字幕Frame queue
    /// 这里 packet queue 是在 VideoState 初始化的时候完成的
    ///videoq、subtitleq、audioq
    is = av_mallocz(sizeof(VideoState));
    
    3.1.3.初始化音频时钟Clock视频时钟Clock外部时钟Clock
     init_clock(&is->vidclk, &is->videoq.serial);
     init_clock(&is->audclk, &is->audioq.serial);
     init_clock(&is->extclk, &is->extclk.serial);
    
    3.1.4.创建读取线程 及函数read_thread
     is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");
    
    3.1.5.创建视频刷新线程 及其函数video_refresh_thread
    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");
    

    3.2 当播放器的初始化没有问题时,调用 read_thread()开始读取媒体的AVPacket;

    • 1.avformat_alloc_context创建上下文
    • 2.ic->interrupt_callback.callback = decode_interrupt_cb;
    • 3.avformat_open_input打开媒体文件
    • 4.日志打印媒体信息av_dump_format(ic, 0, is->filename, 0);
    • 5.查找音频/视频/字母AVStream索引,并把Index记录到 st_index[AVMEDIA_TYPE_NB]
    • 6.设置视频显示的窗口大小 set_default_window_size(codecpar->width, codecpar->height, sar)
    • 7.stream_component_open()打开音频、视频、字幕解码器,并创建相应的解码线程并初始化
    • 8.for 循环读取数据
    3.2.1.avformat_alloc_context创建上下文
     ic = avformat_alloc_context();
    
    3.2.2.ic->interrupt_callback.callback = decode_interrupt_cb;
     ic->interrupt_callback.callback = decode_interrupt_cb;
    
    3.2.3.avformat_open_input打开媒体文件
     err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
    
    3.2.4.日志打印媒体信息av_dump_format(ic, 0, is->filename, 0);
     av_dump_format(ic, 0, is->filename, 0);
    
    3.2.5.查找音频/视频/字母AVStream,并把Index 记录到st_index[AVMEDIA_TYPE_NB]
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream *st = ic->streams[i];
        enum AVMediaType type = st->codecpar->codec_type;
        st->discard = AVDISCARD_ALL;
        if (type >= 0 && ffp->wanted_stream_spec[type] && st_index[type] == -1)
            if (avformat_match_stream_specifier(ic, st, ffp->wanted_stream_spec[type]) > 0)
                st_index[type] = i;
    
        // choose first h264
    
        if (type == AVMEDIA_TYPE_VIDEO) {
            enum AVCodecID codec_id = st->codecpar->codec_id;
            video_stream_count++;
            if (codec_id == AV_CODEC_ID_H264) {
                h264_stream_count++;
                if (first_h264_stream < 0)
                    first_h264_stream = i;
            }
        }
    }
    
    3.2.6.设置视频显示的窗口大小 set_default_window_size(codecpar->width, codecpar->height, sar)
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];
        AVCodecParameters *codecpar = st->codecpar;
        AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);
        if (codecpar->width)
            set_default_window_size(codecpar->width, codecpar->height, sar);
    }  
    
    3.2.7.stream_component_open打开音频、视频、字幕解码器,并创建相应的解码线程并初始化
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_AUDIO]);
    } else {
        ffp->av_sync_type = AV_SYNC_VIDEO_MASTER;
        is->av_sync_type  = ffp->av_sync_type;
    }
    
    ret = -1;
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
        ret = stream_component_open(ffp, st_index[AVMEDIA_TYPE_VIDEO]);
    }
    if (is->show_mode == SHOW_MODE_NONE)
        is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
    
    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) {
        stream_component_open(ffp, st_index[AVMEDIA_TYPE_SUBTITLE]);
    }
    ffp_notify_msg1(ffp, FFP_MSG_COMPONENT_OPEN);
    
    3.2.8.for 循环 读取数据

    for 循环读数据是一段很长的代码,主要包括以下步骤

    • 1.检测是否退出
    • 2.检测是否暂停/继续
    • 3.检测是否需要seek
    • 4.检测video是否为attached_pic
    • 5.检测队列是否有足够的数据
    • 6.检测是否已经播放结束(是否循环播放、是否自动退出)
    • 7.使用 av_read_frame 读取数据包
    • 8.检测数据是否读取完毕
    • 9.检测是否在播放范围
    • 10.将数据插入相应的 frame queue 中(音频/视频/字幕)
    3.2.8.1 检测是否退出
    for (;;) {
            if (is->abort_request)
                break;
                省略代码...
    }
    
    3.2.8.2.检测是否暂停/继续
    if (is->paused != is->last_paused) {
        is->last_paused = is->paused;
        if (is->paused)
            is->read_pause_return = av_read_pause(ic);
        else
            av_read_play(ic);
    }
    
    3.2.8.3.检测是否需要seek
     /// 是否有seek请求
    if (is->seek_req) {
        int64_t seek_target = is->seek_pos;
        int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
        int64_t seek_max    = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
    // FIXME the +-2 is due to rounding being not done in the correct direction in generation
    //      of the seek_pos/seek_rel variables
        ///暂停播放
        ffp_toggle_buffering(ffp, 1);
        ffp_notify_msg3(ffp, FFP_MSG_BUFFERING_UPDATE, 0, 0);
        /// 文件的seek 操作
        ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR,
                    "%s: error while seeking\n", is->ic->filename);
        } else {
            /// seek成功
            if (is->audio_stream >= 0) {
                /// 把packet queue 原先的数据清空,重启编码器,
                packet_queue_flush(&is->audioq);
                /// flush_pkt的作用是刷新,serial+1,重开一个播放序列
                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);
            }
            if (is->seek_flags & AVSEEK_FLAG_BYTE) {
                set_clock(&is->extclk, NAN, 0);
            } else {
                set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
            }
            /// 更新新的播放序列号
            is->latest_video_seek_load_serial = is->videoq.serial;
            is->latest_audio_seek_load_serial = is->audioq.serial;
            is->latest_seek_load_start_at = av_gettime();
        }
        ffp->dcc.current_high_water_mark_in_ms = ffp->dcc.first_high_water_mark_in_ms;
        is->seek_req = 0;
        is->queue_attachments_req = 1;
        is->eof = 0;
    #ifdef FFP_MERGE
        if (is->paused)
            step_to_next_frame(is);
    #endif
        completed = 0;
        SDL_LockMutex(ffp->is->play_mutex);
        if (ffp->auto_resume) {
            is->pause_req = 0;
            if (ffp->packet_buffering)
                is->buffering_on = 1;
            ffp->auto_resume = 0;
            stream_update_pause_l(ffp);
        }
        if (is->pause_req)
            step_to_next_frame_l(ffp);
        SDL_UnlockMutex(ffp->is->play_mutex);
    
        if (ffp->enable_accurate_seek) {
            is->drop_aframe_count = 0;
            is->drop_vframe_count = 0;
            SDL_LockMutex(is->accurate_seek_mutex);
            if (is->video_stream >= 0) {
                is->video_accurate_seek_req = 1;
            }
            if (is->audio_stream >= 0) {
                is->audio_accurate_seek_req = 1;
            }
            SDL_CondSignal(is->audio_accurate_seek_cond);
            SDL_CondSignal(is->video_accurate_seek_cond);
            SDL_UnlockMutex(is->accurate_seek_mutex);
        }
        /// 发送 seek 的消息通知
        ffp_notify_msg3(ffp, FFP_MSG_SEEK_COMPLETE, (int)fftime_to_milliseconds(seek_target), ret);
        ffp_toggle_buffering(ffp, 1);
    }
      
    
    
    3.2.8.4 检测video是否为attached_pic

    AV_DISPOSITION_ATTACHED_PIC 是一个标识,如果一个流种包含这个标识的话,那就说明这个流是 一个*.mp3的文件,并且该流只有一个AVPacket也就是attached_pick,所以这个AVPacket 中存储的就是这个 *.mp3文件的封面图片,所以if条件成立后,将该AVPacket 送进了 Video queue队列中;

     if (is->queue_attachments_req) {
        if (is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
            AVPacket copy = { 0 };
            if ((ret = av_packet_ref(&copy, &is->video_st->attached_pic)) < 0)
                goto fail;
            packet_queue_put(&is->videoq, &copy);
            packet_queue_put_nullpacket(&is->videoq, is->video_stream);
        }
        is->queue_attachments_req = 0;
    }
    
    3.2.8.5 检测队列是否有足够的数据

    播放器同时满足infinite_buffer <1 && !seek_req&& 音频packe> MIN_FRAMES && 音频packe> MIN_FRAMES && 音频packe> MIN_FRAMES 条件,则 表示缓冲队列有足够的包,不需要继续读取数据;
    具体 判断缓冲队列是否有足够的packes 可调用stream_has_enough_packets() 方法,具体细节查看 stream_has_enough_packets() 方法;

      if (ffp->infinite_buffer<1 && !is->seek_req &&
    #ifdef FFP_MERGE
            (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
    #else
            (is->audioq.size + is->videoq.size + is->subtitleq.size > ffp->dcc.max_buffer_size
    #endif
        || (   stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq, MIN_FRAMES)
            && stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq, MIN_FRAMES)
            && stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq, MIN_FRAMES)))) {
        if (!is->eof) {
            ffp_toggle_buffering(ffp, 0);
        }
        /* wait 10 ms */
        SDL_LockMutex(wait_mutex);
        SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
        SDL_UnlockMutex(wait_mutex);
        continue;
    }     
    
    static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue, int min_frames) {
    
    3.2.8.6 检测是否已经播放结束(是否循环播放、是否自动退出)

    (!is->paused || completed)非暂停 或者非播放完毕

    (!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining(&is->sampq) == 0)) 表示没有音频流 或者 音频播放完毕
    (!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0)) 表示 没有视频流 或者 视频播放完毕
    ffplayloop表示该视频流循环播放的次数,默认该值 为1;可以通过参数设置该值;如果是循环播放,则seek到文件的 指定位置,如果start_time 有值,则seekstart_time 位置;

    /// (!is->paused || completed) 非暂停 或者非播放完毕
    /// 没有音频流 或者 音频播放完毕
    ///(!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining(&is->sampq) == 0)) 
    /// 没有视频流 或者 视频播放完毕
    ///(!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0))
    
    if ((!is->paused || completed) &&
                (!is->audio_st || (is->auddec.finished == is->audioq.serial && frame_queue_nb_remaining(&is->sampq) == 0)) &&
                (!is->video_st || (is->viddec.finished == is->videoq.serial && frame_queue_nb_remaining(&is->pictq) == 0))) {
                
        if (ffp->loop != 1 && (!ffp->loop || --ffp->loop)) {
            /// 是否循环播放 (loop == 1表示只播放一次)
            /// seek 到媒体文件头部或 start_time 位置,重新播放
            stream_seek(is, ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0, 0, 0);
        } else if (ffp->autoexit) {
            ret = AVERROR_EOF;
            goto fail;
        } else {
            ffp_statistic_l(ffp);
            if (completed) {
                av_log(ffp, AV_LOG_INFO, "ffp_toggle_buffering: eof\n");
                SDL_LockMutex(wait_mutex);
                // infinite wait may block shutdown
                while(!is->abort_request && !is->seek_req)
                    SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 100);
                SDL_UnlockMutex(wait_mutex);
                if (!is->abort_request)
                    continue;
            } else {
                completed = 1;
                ffp->auto_resume = 0;
    
                // TODO: 0 it's a bit early to notify complete here
                ffp_toggle_buffering(ffp, 0);
                toggle_pause(ffp, 1);
                if (ffp->error) {
                    av_log(ffp, AV_LOG_INFO, "ffp_toggle_buffering: error: %d\n", ffp->error);
                    ffp_notify_msg1(ffp, FFP_MSG_ERROR);
                } else {
                    av_log(ffp, AV_LOG_INFO, "ffp_toggle_buffering: completed: OK\n");
                    ffp_notify_msg1(ffp, FFP_MSG_COMPLETED);
                }
            }
        }
    }
    
    3.2.8.7 使用 av_read_frame 读取数据包

    av_read_frameffmpegAPI,用于获取未解码的数据包;

    /// 读取媒体数据,获得AVPacket(未解码的数据)
    ret = av_read_frame(ic, pkt);
    
    3.2.8.8 检测数据是否读取完毕

    如果读取AVPacket失败,则对音频/视频/字幕 队列 插入 空包,通过解码器刷新buffer, 将缓存的数据都解码出 frame 并处理;

    /// 读取媒体AVPacket
    ret = av_read_frame(ic, pkt);
    if (ret < 0) {
        /// 获取AVPacket 失败
        int pb_eof = 0;
        int pb_error = 0;
        
        if ((ret == AVERROR_EOF || avio_feof(ic->pb)) && !is->eof) {
            /// 如果是文件尾部,则标记
            ffp_check_buffering_l(ffp);
            pb_eof = 1;
            // check error later
        }
        if (ic->pb && ic->pb->error) {
            pb_eof = 1;
            pb_error = ic->pb->error;
        }
       
        if (ret == AVERROR_EXIT) {
             /// 读取Packet 失败的请求,标记异常退出
            pb_eof = 1;
            pb_error = AVERROR_EXIT;
        }
    
        if (pb_eof) {
            /// 音频/视频/字幕 插入空Packet 将缓存的packet都解出来 Frame 并处理
            if (is->video_stream >= 0)
                packet_queue_put_nullpacket(&is->videoq, is->video_stream);
            if (is->audio_stream >= 0)
                packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
            if (is->subtitle_stream >= 0)
                packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
            is->eof = 1;
        }
        if (pb_error) {
             ///(同上,err的情况) 音频/视频/字幕 插入空Packet 将缓存的packet都解出来 Frame 并处理
            if (is->video_stream >= 0)
                packet_queue_put_nullpacket(&is->videoq, is->video_stream);
            if (is->audio_stream >= 0)
                packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
            if (is->subtitle_stream >= 0)
                packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
            is->eof = 1;
            ffp->error = pb_error;
            av_log(ffp, AV_LOG_ERROR, "av_read_frame error: %s\n", ffp_get_error_string(ffp->error));
            // break;
        } else {
            ffp->error = 0;
        }
        if (is->eof) {
            ffp_toggle_buffering(ffp, 0);
            SDL_Delay(100);
        }
        SDL_LockMutex(wait_mutex);
        SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
        SDL_UnlockMutex(wait_mutex);
        ffp_statistic_l(ffp);
        continue;
    } else {
        is->eof = 0;
    }
    
    3.2.8.9 检测是否在播放范围

    stream_start_time是当前AVStream->start_time 获取到的时间,如果没有定义则为AV_NOPTS_VALUE,则无效stream_start_time == 0;
    pkt_ts 表示当前packet的时间戳,pts有效就用pts,无效就用dtspkt_in_play_range 的值为0 或者1;
    pkt_ts - start_time 是否 小于duration为判断,

    /* check if packet is in play range specified by user, then queue, otherwise discard */
    stream_start_time = ic->streams[pkt->stream_index]->start_time;
    pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
    pkt_in_play_range = ffp->duration == AV_NOPTS_VALUE ||
            (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
            av_q2d(ic->streams[pkt->stream_index]->time_base) -
            (double)(ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0) / 1000000
            <= ((double)ffp->duration / 1000000);
    
    if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
        //判断音频packet 是否在播放范围
        packet_queue_put(&is->audioq, pkt);
    } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {
        //判断视频频packet 是否在播放范围
        packet_queue_put(&is->videoq, pkt);
    } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
        //判断字幕packet 是否在播放范围
        packet_queue_put(&is->subtitleq, pkt);
    } else {
        // 不符合条件丢弃
        av_packet_unref(pkt);
    }
    
    3.2.8.10 将数据插入相应的 frame queue 中(音频/视频/字幕)
    /* check if packet is in play range specified by user, then queue, otherwise discard */
    /// 分别将音/视频/字幕 AVPacket 送入 相应的队列
    stream_start_time = ic->streams[pkt->stream_index]->start_time;
    pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
    pkt_in_play_range = ffp->duration == AV_NOPTS_VALUE ||
            (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
            av_q2d(ic->streams[pkt->stream_index]->time_base) -
            (double)(ffp->start_time != AV_NOPTS_VALUE ? ffp->start_time : 0) / 1000000
            <= ((double)ffp->duration / 1000000);
    if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
        packet_queue_put(&is->audioq, pkt);
    } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                && !(is->video_st && (is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC))) {
        packet_queue_put(&is->videoq, pkt);
    } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
        packet_queue_put(&is->subtitleq, pkt);
    } else {
        av_packet_unref(pkt);
    }
    

    至此 stream_open() 开启read_thread线程读取AVPacket的 介绍基本介绍完了;
    本文篇幅较长,需要极大的耐心,需要读者参考ijkplayer 源码一起阅读,会有更好的效果;

    书读百遍其义自见,代码也一样

    相关文章

      网友评论

        本文标题:ijkplayer 源码解析2(数据读线程)

        本文链接:https://www.haomeiwen.com/subject/qmstkdtx.html