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

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 源码一起阅读,会有更好的效果;

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容