整章目录: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_loopmp->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的信号量,这里默认为1if (!(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 rtmpav_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=nullif (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包中都用icav_format_inject_global_side_data(ic);// 是否需要查找流信息,默认为1if (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流的indexint 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) {// 获得解码器idenum 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=0if (!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=1if (!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数据,默认值为0if (!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");}// 注意:这里是一个死循环,用于不停的读流中的packetfor (;;) {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);//将音频流信息拷贝到新的AVCodecContextret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);//将avStream->time_base赋值到AVCodecContext->pkt_timebaseav_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);// 查找用户对解码器的设置,并返回给optsopts = 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.   设置播放器
  • 2.   创建一个ijkmp_msg_loop线程:运行消息队列message_loop
  • 3.   创建一个video_refresh_thread线程:视频界面刷新
  • 4.   创建一个read_thread线程:网络流中读取AvPacket数据,且又创建了4个子线程。
  • 在read_thread线程中创建的4个子线程
  • 5.   分别创建三个audio_thread、video_thread、subtitle_thread线程:将对应类型的AvPacket数据解码为Frame(帧)。已更新,请看:Android --- IjkPlayer 阅读native层源码之如何将AvPacket数据解码出一帧数据(六)
  • 6.   创建一个aout_thread线程: 负责将解码后的音频帧数据推送给Android播放。已更新,请看:Android --- IjkPlayer 阅读native层源码之解码成功后的音频数据如何发送回Android播放(九)

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

Android ---- Ijkplayer阅读native层源码之IjkMediaPlayer_prepareAsync(五)相关推荐

  1. Android --- IjkPlayer 阅读native层源码之解码成功后的音频数据如何发送回Android播放(九)

    整章目录:Android------- IjkPlayer 源码学习目录 本篇会有很多源代码,请注意阅读每行代码上面的注释. 本篇介绍的主要内容为上图红框圈起部分: 在前面介绍了如何将一个AvPack ...

  2. Android FM模块学习之四源码分析(五)

    前几章我们分析了FM模块的几个主要的类文件,今天要分析的是:FMTransceiver.java public class FmTransceiver {/* Primary FM States :* ...

  3. android优化中国风应用、完整NBA客户端、动态积分效果、文件传输、小说阅读器等源码...

    Android精选源码 android拖拽下拉关闭效果源码 一款优雅的中国风Android App源码 EasySignSeekBar一个漂亮而强大的自定义view15 android仿蘑菇街,蜜芽宝 ...

  4. 基于Android的看小说APP源码Android本科毕业设计Android小说阅读器、小说APP源码

    基于kotlin + 协程 + MVVM 模式来编写的看小说APP. 完整代码下载地址:基于Android的看小说APP源码Android本科毕业设计Android小说阅读器.小说APP源码 主要框架 ...

  5. android新闻项目、饮食助手、下拉刷新、自定义View进度条、ReactNative阅读器等源码...

    Android精选源码 Android仿照36Kr官方新闻项目课程源码 一个优雅美观的下拉刷新布局,众多样式可选 安卓版本的VegaScroll滚动布局 android物流详情的弹框 健身饮食记录助手 ...

  6. Android四大组件之bindService源码实现详解

        Android四大组件之bindService源码实现详解 Android四大组件源码实现详解系列博客目录: Android应用进程创建流程大揭秘 Android四大组件之bindServic ...

  7. 带着问题分析Framework层源码(一):按键音声音太小,我们该如何增大?

    作为一名Android开发人员,对源码的阅读是必不可少的.但是Android源码那么庞大,从何开始阅读,如何开始阅读,很多人都会感觉无从下手,今天我来带着问题,去带大家分析一下Android源码,并解 ...

  8. Android Input子系统-含实例源码

    Android Input子系统-含实例源码 1 Input子系统作用 Android很多外设都是用到输入输出设备,比如touchscreen,键盘,音量键等,输入 设备对应Android 框架是An ...

  9. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

最新文章

  1. HarmonyOS 设置图标在Text 旁边
  2. 盛大文学难逃“垄断”嫌疑,完美文学虎口夺食
  3. java关闭ie提示_java 关闭IE
  4. addShutdownHook钩子
  5. 由java的八个基本数据类型说开去
  6. 赵加雨:追求极致的习惯让我受益匪浅
  7. 洛谷P1035 [NOIP2002 普及组] 级数求和
  8. 信息学奥赛一本通(1127:图像旋转)
  9. postgresql数据库导入导出
  10. Android MediaPlayer 实现音乐播放器
  11. 红米note10 pro刷机
  12. 安装imageai,tensorflow
  13. Android相对布局简单案例(附完整源码)
  14. 【干货】从QQ群起家的情趣商城站长之路
  15. 产品经理天马行空,表格组件应对自如
  16. pandoc输出中文pdf cmd命令记录
  17. Windows 的应急事件分类-
  18. 小赛毛游C记-初识C语言(2)
  19. 国内安卓统一推送通道
  20. 读书笔记---《如何高效学习》

热门文章

  1. 数值计算原理_变压器差动保护的基本原理及逻辑图
  2. 穿梭车的立体仓库作业分析
  3. ZT: WinXP极速关机
  4. 福师《计算机应用基础》期末考试a卷及答案,计算机应用基础试卷及答案A
  5. linux黄金变量,Linux变量
  6. 卷积神经网络流程图_【专利解密】旷视科技的小型神经网络是什么
  7. 中国科学院计算机院士,中国科学院院士王怀民莅临计算机学院交流指导
  8. nagios是什么_什么是Nagios?
  9. Oracle函数篇 - REPLACE()函数
  10. shell 学习笔记 常用命令 tar cpio gzip zip