Android ---- Ijkplayer阅读native层源码之IjkMediaPlayer_prepareAsync(五)

整章目录:Android------- IjkPlayer 源码学习目录

本篇会有很多源代码,请注意阅读每行代码上面的注释。

本篇介绍的主要内容为上图红框圈起部分:

IjkMediaPlayer_prepareAsync的作用为播放器播放前做准备。其被Java层的IjkMediaPlayer.prepareAsync调用。

IjkMediaPlayer_prepareAsync :

static int ijkmp_prepare_async_l(IjkMediaPlayer *mp)
{

    省略。。。。
    // 1.向消息链表发送一个FFP_MSG_PLAYBACK_STATE_CHANGED消息,改变IjkMediaPlayer的状态为MP_STATE_ASYNC_PREPARING,向native层发送一次测试消息
    ijkmp_change_state_l(mp, MP_STATE_ASYNC_PREPARING);

    // 2.向消息链表发送一个FFP_MSG_FLUSH消息,表示向向Java层发送一次测试通信。
    msg_queue_start(&mp->ffplayer->msg_queue);

    // 3.创建一个线程用来运行消息队列ijkmp_msg_loop
    mp->msg_thread = SDL_CreateThreadEx(&mp->_msg_thread, ijkmp_msg_loop, mp, "ff_msg_loop");

    // 4.设置请求uri、创建和初始化音轨、打开数据流,启动视频刷新线程和读取数据线程
    int retval = ffp_prepare_async_l(mp->ffplayer, mp->data_source);
 
    return 0;
}

上面为省略版的ijkmp_prepare_async_l,一共做了4件事。下面将按照事件顺序阅读。

事件1、事件2:

向消息链表中发送两条消息,一条测试Java层的消息处理机制是否通了,一条测试native的消息处理机制是否通了。可以忽略不用管,估计是开发者自己用的。

事件3:创建一个线程来运行native的消息处理机制:详细请看:Android --- Ijkplayer源码阅读native层之自定义消息处理机制(四)

事件4:设置请求uri、创建和初始化音轨、打开数据流,启动视频刷新线程和读取数据线程;

ffp_prepare_async_l:

int ffp_prepare_async_l(FFPlayer *ffp, const char *file_name)
{
    省略代码。。。。

    //如果是rtmp或者rtsp开头的,表示直播,将超时选项置空
    if (av_stristart(file_name, "rtmp", NULL) ||
        av_stristart(file_name, "rtsp", NULL)) {

        av_dict_set(&ffp->format_opts, "timeout", NULL, 0);
    }

    // 防止名字过长
    if (strlen(file_name) + 1 > 1024) {
        //返回将处理传递的URL的协议的名称。如果没有找到针对给定URL的协议,则返回NULL。
        if (avio_find_protocol_name("ijklongurl:")) {
            av_dict_set(&ffp->format_opts, "ijklongurl-url", file_name, 0);
            file_name = "ijklongurl:";
        }
    }

    // 将所有的选项设置到ffp对象的AvClass中
    av_opt_set_dict(ffp, &ffp->player_opts);

    // 判断是否有音频信号量
    if (!ffp->aout) {
        // 创建并初始化音轨
        ffp->aout = ffpipeline_open_audio_output(ffp->pipeline, ffp);
    }

    // 打开数据流,并启动视频刷新线程和读取数据线程
    VideoState *is = stream_open(ffp, file_name, NULL);
    return 0;
}

上面加了注解,找到我们的主线stream_open:

static VideoState *stream_open(FFPlayer *ffp, const char *filename, AVInputFormat *iformat)
{

    VideoState *is;
    // 创建VideoState结构体
    is = av_mallocz(sizeof(VideoState));

// 如果是Android平台
#if defined(__ANDROID__)
    
    // SoundTouch是一个开源的音频处理库,主要实现包含变速、变调、变速同时变调等三个功能模块
    // 是否开启SoundTouch,在这里只有变速,是否使用SoundTouch来改变音频速度
    if (ffp->soundtouch_enable) {
        is->handle = ijk_soundtouch_create();
    }
#endif


    // ffp->pictq_size=3
    // 初始化is中的视频帧、音频帧、字幕帧队列,以一帧为单位存储
    if (frame_queue_init(&is->pictq, &is->videoq, ffp->pictq_size, 1) < 0)
        goto fail;
    if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
        goto fail;
    if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
        goto fail;

    // 初始化视频、音频、字幕链表, 
    // 一帧中可能会用多个AvPacket构成,该链表以一AvPacket为单位存储
    if (packet_queue_init(&is->videoq) < 0 ||
        packet_queue_init(&is->audioq) < 0 ||
        packet_queue_init(&is->subtitleq) < 0)
        goto fail;

    // 创建控制线程的条件变量,类似于Java的信号量,这里默认为1
    if (!(is->continue_read_thread = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        goto fail;
    }

    // 初始化锁状态 
    init_clock(&is->vidclk, &is->videoq.serial);
    init_clock(&is->audclk, &is->audioq.serial);
    init_clock(&is->extclk, &is->extclk.serial);
    
    // 用户设置的播放器音量大小,av_clip限制其值在0到100之间
    ffp->startup_volume = av_clip(ffp->startup_volume, 0, 100);

    // 将0~100的音量转化为系统对应的音量大小。
    ffp->startup_volume = av_clip(SDL_MIX_MAXVOLUME * ffp->startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
    // 设置音量
    is->audio_volume = ffp->startup_volume;
    // 标识是否无声
    is->muted = 0;
    // 音视频的同步类型:1.按音频时间轴;2.视频时间轴;3.外部时间轴
    is->av_sync_type = ffp->av_sync_type;
  
    // 请求暂停播放,1为暂停
    is->pause_req = !ffp->start_on_prepared; 

    // 启动视频刷新线程
    is->video_refresh_tid = SDL_CreateThreadEx(&is->_video_refresh_tid, video_refresh_thread, ffp, "ff_vout");

    //注意:开启网络流数据读取线程
    is->read_tid = SDL_CreateThreadEx(&is->_read_tid, read_thread, ffp, "ff_read");


    if (ffp->async_init_decoder && !ffp->video_disable && ffp->video_mime_type && strlen(ffp->video_mime_type) > 0
                    && ffp->mediacodec_default_name && strlen(ffp->mediacodec_default_name) > 0) {
      // 用户是否选择使用Android的MediaCodec类来编解码视频
        if (ffp->mediacodec_all_videos || ffp->mediacodec_avc || ffp->mediacodec_hevc || ffp->mediacodec_mpeg2) {
            decoder_init(&is->viddec, NULL, &is->videoq, is->continue_read_thread);
            // 创建Android的的MediaCodec类
            ffp->node_vdec = ffpipeline_init_video_decoder(ffp->pipeline, ffp);
        }
    }
    return is;

// 如果失败
fail:
    // 暂停
    is->abort_request = true;
    if (is->video_refresh_tid)
        // 停止刷新视频界面
        SDL_WaitThread(is->video_refresh_tid, NULL);

}

上面添加了注释,其中有两个主要步骤:

1、video_refresh_thread 刷新视频界面线程

2、read_thread  网络流数据读取线程

这两个步骤都挺复杂的,且我们都还没有拿到音视频数据,所以决定这里又要欠一篇细读----刷新视频界面线程---------已更新,请看:Android --- IjkPlayer 阅读native层源码之如何刷新视频的播放界面(七)

下面细读网络流数据读取线程------read_thread:

static int read_thread(void *arg)
{

    省略。。。。

    // FFmpeg基本操作:我理解为创建操作ffmpeg的上下文
    ic = avformat_alloc_context();

    // 设置连接中断回调
    ic->interrupt_callback.callback = decode_interrupt_cb;
    ic->interrupt_callback.opaque = is;

    // 扫描全部的ts流的"Program Map Table"表, 可以知道当前频道包含多少个Video、共多少个Audio和其他数据
    if (!av_dict_get(ffp->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
        av_dict_set(&ffp->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
        scan_all_pmts_set = 1;
    }

    // 如果是直播,将超时选项置空
    if (av_stristart(is->filename, "rtmp", NULL) ||
        av_stristart(is->filename, "rtsp", NULL)) {
        // There is total different meaning for 'timeout' option in rtmp
        av_log(ffp, AV_LOG_WARNING, "remove 'timeout' option for rtmp.\n");
        av_dict_set(&ffp->format_opts, "timeout", NULL, 0);
    }

    //是否设置了跳帧????
    if (ffp->skip_calc_frame_rate) {
        av_dict_set_int(&ic->metadata, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0);
        av_dict_set_int(&ffp->format_opts, "skip-calc-frame-rate", ffp->skip_calc_frame_rate, 0);
    }

    // 雷霄骅 https://blog.csdn.net/leixiaohua1020/article/details/39702113
    // 查找用于输入的设备,我们使用的是Android,所以不会用到,iformat_name=null
    if (ffp->iformat_name)
        is->iformat = av_find_input_format(ffp->iformat_name);

    // FFmpeg基本操作: 打开一个普通的视频文件
    err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);
    
    // 设置播放器选项
    if (scan_all_pmts_set)
        av_dict_set(&ffp->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);

    is->ic = ic;

    // 生成丢失的pts,即使它需要解析未来的帧
    if (ffp->genpts)
        ic->flags |= AVFMT_FLAG_GENPTS;

    // FFmpeg基本操作:这个函数将导致全局端数据被注入到每个流的下一个包中,以及在任何后续的搜索之后。即ic,放入一个全局变量中,以后每个AvPacket包中都用ic
    av_format_inject_global_side_data(ic);

    // 是否需要查找流信息,默认为1
    if (ffp->find_stream_info) {
        //获得用户对流中编解码器的设置
        AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts);
        // 流的个数,如:音频流、视频流
        int orig_nb_streams = ic->nb_streams;
        do {
            // 判断是否是本地数据
            if (av_stristart(is->filename, "data:", NULL) && orig_nb_streams > 0) {
                for (i = 0; i < orig_nb_streams; i++) {
                    if (!ic->streams[i] || !ic->streams[i]->codecpar || ic->streams[i]->codecpar->profile == FF_PROFILE_UNKNOWN) {
                        break;
                    }
                }

                if (i == orig_nb_streams) {
                    break;
                }
            }
            // FFmpeg基本操作:探测,寻找流信息
            err = avformat_find_stream_info(ic, opts);
        } while(0);

    }

 
    // 如果设置了开始播放的时间
    if (ffp->start_time != AV_NOPTS_VALUE) {
        int64_t timestamp;

        timestamp = ffp->start_time;
        /* add the stream start time */
        if (ic->start_time != AV_NOPTS_VALUE)
            timestamp += ic->start_time;
        // 设置播放进度
        ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);
    }

    //是不是直播
    is->realtime = is_realtime(ic);

    // https://www.cnblogs.com/renhui/p/10392721.html
    // 打印音视频Meta数据
    av_dump_format(ic, 0, is->filename, 0);

    //记录视频流的个数
    int video_stream_count = 0;
    //记录h264个数
    int h264_stream_count = 0;
    //记录第一个h264流的index
    int first_h264_stream = -1;

    //寻找h264第一帧
    for (i = 0; i < ic->nb_streams; i++) {
        AVStream *st = ic->streams[i];

        // 获取流类型
        enum AVMediaType type = st->codecpar->codec_type;

        // https://blog.csdn.net/oncealong/article/details/95068742
        // 过滤调所有帧
        // 猜测因为下面需要遍历流,找到其编解码器和设置一些用户参数,还不会解码,所以先过滤调所有帧
        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) {
            // 获得解码器id
            enum AVCodecID codec_id = st->codecpar->codec_id;
            video_stream_count++;
            // 如果是h264解码器
            if (codec_id == AV_CODEC_ID_H264) {
                h264_stream_count++;
                // 记录第一个h264视频流的序列号
                if (first_h264_stream < 0)

                    first_h264_stream = i;
            }
        }
    }

    //FFmpeg基本操作:寻找视频、音频、字幕开始帧
    //0=0
    if (!ffp->video_disable)
        st_index[AVMEDIA_TYPE_VIDEO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
    //1=1
    if (!ffp->audio_disable)
        st_index[AVMEDIA_TYPE_AUDIO] =
            av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                st_index[AVMEDIA_TYPE_AUDIO],
                                st_index[AVMEDIA_TYPE_VIDEO],
                                NULL, 0);
    if (!ffp->video_disable && !ffp->subtitle_disable)
        st_index[AVMEDIA_TYPE_SUBTITLE] =
            av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
                                st_index[AVMEDIA_TYPE_SUBTITLE],
                                (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
                                 st_index[AVMEDIA_TYPE_AUDIO] :
                                 st_index[AVMEDIA_TYPE_VIDEO]),
                                NULL, 0);

    is->show_mode = ffp->show_mode;

    // 注意:stream_component_open---开启一个的线程(eg,audio_thread处理音频)来将从网络流中读取的AvPacket数据解码为一帧数据,并存入缓存队列中。
    // av_sync_type默认值为AV_SYNC_AUDIO_MASTER,表示让视频去同步音频,即改变视频播放持续时间或者丢帧,来追赶或等待音频播放。

    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]);
    }


    // https://www.cnblogs.com/renhui/p/10392721.html
    // 是否延迟初始化视频meta数据,默认值为0
    if (!ffp->ijkmeta_delay_init) {
        ijkmeta_set_avformat_context_l(ffp->meta, ic);
    }

    // 设置比特率,会在Java展示这项数据
    ffp->stat.bit_rate = ic->bit_rate;

    // 将流序列号存储到meta字典中。
    if (st_index[AVMEDIA_TYPE_VIDEO] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_VIDEO_STREAM, st_index[AVMEDIA_TYPE_VIDEO]);
    if (st_index[AVMEDIA_TYPE_AUDIO] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_AUDIO_STREAM, st_index[AVMEDIA_TYPE_AUDIO]);
    if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0)
        ijkmeta_set_int64_l(ffp->meta, IJKM_KEY_TIMEDTEXT_STREAM, st_index[AVMEDIA_TYPE_SUBTITLE]);


    // 监控的缓存队列
    if (is->audio_stream >= 0) {
        is->audioq.is_buffer_indicator = 1;
        is->buffer_indicator_queue = &is->audioq;
    } else if (is->video_stream >= 0) {
        is->videoq.is_buffer_indicator = 1;
        is->buffer_indicator_queue = &is->videoq;
    } else {
        assert("invalid streams");
    }


    // 注意:这里是一个死循环,用于不停的读流中的packet
    for (;;) {

        if (is->abort_request)
            break;

        //是否拖动进度条
        if (is->seek_req) {

            // 如果标志包含AVSEEK_FLAG_BYTE,那么所有时间戳都是以字节为单位的,并且是文件位置(这可能不是所有demuxer都支持)。
            // 如果标志包含AVSEEK_FLAG_FRAME,那么所有时间戳都在stream_index流中的帧中(这可能不是所有demuxer都支持)。
            // 否则,所有时间戳都以stream_index选择的流的单位为单位,或者如果stream_index为-1,则以AV_TIME_BASE单位为单位。
            // 如果标志包含AVSEEK_FLAG_ANY,那么非关键帧将被视为关键帧(这可能不是所有的demuxer都支持)。
            // 如果标志包含AVSEEK_FLAG_BACKWARD,则忽略它。

            //寻找时间戳ts。进行搜索的目的是使所有活动流都可以成功显示的点最接近ts,并且在min/max_ts内
            ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
            。。。。。。。。
        }


        // 如果队列满了
        /* if the queue are full, no need to read more */
        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;
        }

        pkt->flags = 0;

        // FFmpeg基本操作:从网络流中读取一个AVPacket数据
        ret = av_read_frame(ic, pkt);

        // 读取失败
        if (ret < 0) {
            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
            }
        }

        // 如果中断,清空缓存队列。 当ijklivehook.av_read_frame读取失败时。会出现该标签,而ijklivehook应该是直播有关的类,
        if (pkt->flags & AV_PKT_FLAG_DISCONTINUITY) {
            if (is->audio_stream >= 0) {
                packet_queue_put(&is->audioq, &flush_pkt);
            }
            if (is->subtitle_stream >= 0) {
                packet_queue_put(&is->subtitleq, &flush_pkt);
            }
            if (is->video_stream >= 0) {
                packet_queue_put(&is->videoq, &flush_pkt);
            }
        }

        /* check if packet is in play range specified by user, then queue, otherwise discard */
        // 开始播放时间
        stream_start_time = ic->streams[pkt->stream_index]->start_time;
        // 该AvPacket中的数据(帧)的显示时间
        pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
        // 注意:这里计算该AvPacket中的数据(帧)的播放时间是否具有时效性,即没有错过其播放时间
        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);

        // 如果AvPacket包具有时效性,将读取的AvPacket包放入对应的缓存队列中,否则丢弃。
        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);
        }

        // 计算此时音视频缓存信息,eg,缓存大小、缓存未播放的时间等
        ffp_statistic_l(ffp);

        // https://www.cnblogs.com/renhui/p/10392721.html
        // 设置视频meta信息
        if (ffp->ijkmeta_delay_init && !init_ijkmeta &&
                (ffp->first_video_frame_rendered || !is->video_st) && (ffp->first_audio_frame_rendered || !is->audio_st)) {
            ijkmeta_set_avformat_context_l(ffp->meta, ic);
            init_ijkmeta = 1;
        }

        // 下面这一段代码作用:
        // 1. 更新UI----缓存进度条的值。注意不是播放进度条
        // 2. 让播放器首次由暂停转为播放,即开屏播发

        // 系统是否开启packet数据缓存
        if (ffp->packet_buffering) {
            // io记号器=当前时间
            io_tick_counter = SDL_GetTickHR();
            // 如果还没播放的时候,即开屏
            if ((!ffp->first_video_frame_rendered && is->video_st) || (!ffp->first_audio_frame_rendered && is->audio_st)) {
                // 如果缓存时间超过50ms,即第一次开屏播放需要等待50ms,才能播放
                if (abs((int)(io_tick_counter - prev_io_tick_counter)) > FAST_BUFFERING_CHECK_PER_MILLISECONDS) {
                    prev_io_tick_counter = io_tick_counter;
                    // 设置缓存时间
                    ffp->dcc.current_high_water_mark_in_ms = ffp->dcc.first_high_water_mark_in_ms;
                    // 1.更新UI----缓存进度条的值,注意不是播放进度条.
                    // 2.重要:播放器首次被切换为播放状态
                    ffp_check_buffering_l(ffp);
                }
            } else {
                // 如果缓存时间超过500ms,即播放开始后,每等待500ms间隔,更新一次UI---缓存进度条的值
                if (abs((int)(io_tick_counter - prev_io_tick_counter)) > BUFFERING_CHECK_PER_MILLISECONDS) {
                    prev_io_tick_counter = io_tick_counter;
                    ffp_check_buffering_l(ffp);
                }
            }
        }
    }
   省略。。。。
}

把重要的步骤提取出来,如下:

// 1.FFmpeg基本操作:获得操作ffmpeg的上下文   
   ic = avformat_alloc_context();

// 2.FFmpeg基本操作: 打开一个普通的视频文件
   err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

// 3.FFmpeg基本操作:探测,寻找流信息
   err = avformat_find_stream_info(ic, opts);

// 4.FFmpeg基本操作:寻找视频、音频、字幕开始帧
   av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
// 在函数stream_component_open中
   stream_component_open(...){

     // 5.FFmpeg基本操作:创建一个解码器上下文
        avctx = avcodec_alloc_context3(NULL);

     // 6.FFmpeg基本操作:寻找解码器
        codec = avcodec_find_decoder(avctx->codec_id); 
     
     // 7.FFmpeg基本操作:初始化一个视音频编解码器上下文的AVCodecContext。操作解码器
        ret = avcodec_open2(avctx, codec, &opts))

     // 创建一个线程:处理音频,将AvPacket数据转换为一帧Frame数据
        audio_thread{
            for(;;){  

                // 在函数decoder_decode_frame中
                decoder_decode_frame(...){

                    // 8.FFmpeg基本操作:将一个AVPacket数据包放入解码器中解码
                       avcodec_send_packet(d->avctx, &pkt)
                    
                    // 音频一个AvPacket可能会有多帧
                    for(){
                        // 9.FFmpeg基本操作:从解码器中拿到解码成功后的一帧数据。 
                           ret = avcodec_receive_frame(d->avctx, frame);
                    }

                }
                
            }
         }
     // 创建一个线程:处理视频
         video_thread{
            参考音频线程
         }
     // 创建一个线程:处理字幕
        subtitle_thread{
            参考音频线程
        }

   }
   
       
// 死循环
for (;;) {
   // 10.FFmpeg基本操作:从网络流中读取一个AVPacket数据
   ret = av_read_frame(ic, pkt);
}

上面为FFmpeg的基本操作,没啥好说的,下面细说其中的重要方法:stream_component_open其作用------开启一个的线程(eg,audio_thread处理音频)来将从网络流中读取的AvPacket数据解码为帧数据,并存入缓存队列中。

以音频为例:

static int stream_component_open(FFPlayer *ffp, int stream_index)
{
    省略。。。。
    // FFmpeg基本操作:创建一个解码器上下文
    avctx = avcodec_alloc_context3(NULL);

    //将音频流信息拷贝到新的AVCodecContext
    ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);

    //将avStream->time_base赋值到AVCodecContext->pkt_timebase
    av_codec_set_pkt_timebase(avctx, ic->streams[stream_index]->time_base);

    // FFmpeg基本操作:寻找解码器
    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;
        case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = ffp->subtitle_codec_name; break;
        case AVMEDIA_TYPE_VIDEO   : is->last_video_stream    = stream_index; forced_codec_name = ffp->video_codec_name; break;
        default: break;
    }

    //如果有用户要求使用的解码器名字,通过名字找解码器
    if (forced_codec_name)
        codec = avcodec_find_decoder_by_name(forced_codec_name);
    
    avctx->codec_id = codec->id;
    // 如果当前分辨率比最大分辨率大
    if(stream_lowres > av_codec_get_max_lowres(codec)){
        stream_lowres = av_codec_get_max_lowres(codec);
    }

    //设置分辨率。--------注意
    av_codec_set_lowres(avctx, stream_lowres);

    // 查找用户对解码器的设置,并返回给opts
    opts = filter_codec_opts(ffp->codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);

    if (!av_dict_get(opts, "threads", NULL, 0))
        av_dict_set(&opts, "threads", "auto", 0);
    if (stream_lowres)
        av_dict_set_int(&opts, "lowres", stream_lowres, 0);

    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO)
        //表示该frame的引用计数,即有另外一帧将该帧用作参考帧,且将参考帧返回给调用者
        av_dict_set(&opts, "refcounted_frames", "1", 0);

    //  FFmpeg基本操作:初始化一个视音频编解码器上下文的AVCodecContext。操作解码器
    if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {
    }

    //丢弃 avi 中的无效数据
    ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:

        // 注意:关键步骤
        // 1.创建并打开Android音频播放器
        // 2.开启线程aout_thread----实时将native层对播放器的状态设置传到Android音频播放。处理音频帧数据(如变速),即创建音频播放器,并开启线程向播放器推送音频数据。
        if ((ret = audio_open(ffp, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)


        // 开启一个线程解码;将packect数据转化为frame数据,并存放在&is->sampq中
        if ((ret = decoder_start(&is->auddec, audio_thread, ffp, "ff_audio_dec")) < 0)

    // 视频
    case AVMEDIA_TYPE_VIDEO:
          
          省略。。。。
    // 字幕
    case AVMEDIA_TYPE_SUBTITLE:
        
          省略。。。。

}

不管是音频、视频还是字幕,都是如下步骤:

1. 创建解码器上下文

2.找到要使用的解码器

3. 设置解码器

4. 创建音频播放器,并开启新线程不停的向播放器推送音频解码后的数据(只有音频会有该步骤)

5. 开启线程使用解码器解码。

OK,本来还想继续细说步骤4和5的,发现这篇有点多了,后面在新开篇张单独聊。

下面总结下IjkMediaPlayer_prepareAsync函数做了哪些:

最后,提一下:上面提到的线程都是无限循环。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值