《Android FFmpeg 播放器开发梳理 》:
播放器初始化与解复用流程
这一章,我们来讲解播放器解复用(从文件中读取数据包)的流程。在讲解播放器的读数据包流程之前,我们先定义一个播放器状态结构体,用来记录播放器的各种状态。
播放器状态结构体
首先,我们定义一个结构体,用于记录播放器的开始、暂停、定位等各种状态标志,以及重置结构体的方法:
1/** 2 * 播放器状态结构体 3 */ 4typedef struct PlayerState { 5 6 AVDictionary *sws_dict; // 视频转码option参数 7 AVDictionary *swr_opts; // 8 AVDictionary *format_opts; // 解复用option参数 9 AVDictionary *codec_opts; // 解码option参数10 AVDictionary *resample_opts; // 重采样option参数1112 const char *audioCodecName; // 指定音频解码器名称13 const char *videoCodecName; // 指定视频解码器名称1415 int abortRequest; // 退出标志16 int pauseRequest; // 暂停标志17 SyncType syncType; // 同步类型18 int64_t startTime; // 播放起始位置19 int64_t duration; // 播放时长20 int realTime; // 判断是否实时流21 int audioDisable; // 是否禁止音频流22 int videoDisable; // 是否禁止视频流23 int displayDisable; // 是否禁止显示2425 int fast; // 解码上下文的AV_CODEC_FLAG2_FAST标志26 int genpts; // 解码上下文的AVFMT_FLAG_GENPTS标志27 int lowres; // 解码上下文的lowres标志2829 float playbackRate; // 播放速度30 float playbackPitch; // 播放音调3132 int seekByBytes; // 是否以字节定位33 int seekRequest; // 定位请求34 int seekFlags; // 定位标志35 int64_t seekPos; // 定位位置36 int64_t seekRel; // 定位偏移3738 int autoExit; // 是否自动退出39 int loop; // 循环播放40 int mute; // 静音播放41 int frameDrop; // 舍帧操作4243} PlayerState;4445/**46 * 重置播放器状态结构体47 * @param state48 */49inline void resetPlayerState(PlayerState *state) {5051 av_opt_free(state);5253 av_dict_free(&state->sws_dict);54 av_dict_free(&state->swr_opts);55 av_dict_free(&state->format_opts);56 av_dict_free(&state->codec_opts);57 av_dict_free(&state->resample_opts);58 av_dict_set(&state->sws_dict, "flags", "bicubic", 0);5960 if (state->audioCodecName != NULL) {61 av_freep(&state->audioCodecName);62 state->audioCodecName = NULL;63 }64 if (state->videoCodecName != NULL) {65 av_freep(&state->videoCodecName);66 state->videoCodecName = NULL;67 }68 state->abortRequest = 1;69 state->pauseRequest = 0;70 state->seekByBytes = 0;71 state->syncType = AV_SYNC_AUDIO;72 state->startTime = AV_NOPTS_VALUE;73 state->duration = AV_NOPTS_VALUE;74 state->realTime = 0;75 state->audioDisable = 0;76 state->videoDisable = 0;77 state->displayDisable = 0;78 state->fast = 0;79 state->genpts = 0;80 state->lowres = 0;81 state->playbackRate = 1.0;82 state->playbackPitch = 1.0;83 state->seekRequest = 0;84 state->seekFlags = 0;85 state->seekPos = 0;86 state->seekRel = 0;87 state->seekRel = 0;88 state->autoExit = 0;89 state->loop = 0;90 state->frameDrop = 0;91}
播放器状态结构体的作用是用来给解复用、解码、同步等流程共享状态的,方便统一播放器的状态。
初始化以及解复用
我们在播放器调用 prepare() 时创建一个线程用来初始化解码器、打开音视频输出设备和音视频同步渲染线程等出来了,在准备完成后,等待播放器调用 start() 方法更新PlayerState 的 pauseRequest 标志,进入读数据包流程。
读数据包线程执行逻辑如下:
初始化流程
-
利用avformat_alloc_context()方法创建解复用上下文并设置解复用中断回调
-
利用 avformat_open_input()方法打开url,url可以是本地文件,也可以使网络媒体流
-
在成功打开文件之后,我们需要利用avformat_find_stream_info()方法查找媒体流信息
-
如果开始播放的位置不是AV_NOPTS_VALUE,即从文件开头开始的话,需要先利用avformat_seek_file方法定位到播放的起始位置
-
查找音频流、视频流索引,然后根据是否禁用音频流、视频流判断的设置,分别准备解码器对象
-
当我们准备好解码器之后,通过媒体播放器回调播放器已经准备。
-
判断音频解码器是否存在,通过openAudioDevice方法打开音频输出设备
-
判断视频解码器是否存在,打开视频同步输出设备
其中,第7、第8步骤,我们需要根据实际情况,重新设定同步类型。同步有三种类型,同步到音频时钟、同步到视频时钟、同步到外部时钟。默认是同步到音频时钟,如果音频解码器不存在,则根据需求同步到视频时钟还是外部时钟。
解复用流程
经过前面的初始化之后,我们就可以进入读数据包流程。读数据包流程如下:
-
判断是否退出播放器
-
判断暂停状态是否发生改变,设置解复用是否暂停还是播放 —— av_read_pause 和 av_read_play
-
处理定位状态。利用avformat_seek_file()方法定位到实际的位置,如果定位成功,我们需要清空音频解码器、视频解码器中待解码队列的数据。处理完前面的逻辑,我们需要更新外部时钟,并且更新视频同步刷新的时钟
-
根据是否设置attachmentRequest标志,从视频流取出attached_pic的数据包
-
判断解码器待解码队列的数据包如果大于某个数量,则等待解码器消耗
-
读数据包
-
判断数据包是否读取成功。如果没成功,则判断读取的结果是否AVERROR_EOF,即结尾标志。如果到了结尾,则入队一个空的数据包。如果读取出错,则直接退出读数据包流程。如果都不是,则判断解码器中的待解码数据包、待输出帧是否存在数据,如果都不存在数据,则判断是否跳转至起始位置还是判断是否自动退出,或者是继续下一轮读数据包流程。
-
根据取得的数据包判断是否在播放范围内的数据包。如果在播放范围内,则根据数据包的媒体流索引判断是否入队数据包舍弃。
至此,解复用流程就完成了。
整个线程执行体的代码如下:
1int MediaPlayer::readPackets() { 2 int ret = 0; 3 AVDictionaryEntry *t; 4 AVDictionary **opts; 5 int scan_all_pmts_set = 0; 6 7 // 准备解码器 8 mMutex.lock(); 9 do { 10 // 创建解复用上下文 11 pFormatCtx = avformat_alloc_context(); 12 if (!pFormatCtx) { 13 av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n"); 14 ret = AVERROR(ENOMEM); 15 break; 16 } 17 18 // 设置解复用中断回调 19 pFormatCtx->interrupt_callback.callback = avformat_interrupt_cb; 20 pFormatCtx->interrupt_callback.opaque = playerState; 21 if (!av_dict_get(playerState->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) { 22 av_dict_set(&playerState->format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE); 23 scan_all_pmts_set = 1; 24 } 25 26 // 设置rtmp/rtsp的超时值 27 if (av_stristart(url, "rtmp", NULL) || av_stristart(url, "rtsp", NULL)) { 28 // There is total different meaning for 'timeout' option in rtmp 29 av_log(NULL, AV_LOG_WARNING, "remove 'timeout' option for rtmp.\n"); 30 av_dict_set(&playerState->format_opts, "timeout", NULL, 0); 31 } 32 33 // 打开文件 34 ret = avformat_open_input(&pFormatCtx, url, NULL, &playerState->format_opts); 35 if (ret < 0) { 36 printError(url, ret); 37 ret = -1; 38 break; 39 } 40 41 if (scan_all_pmts_set) { 42 av_dict_set(&playerState->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE); 43 } 44 45 if ((t = av_dict_get(playerState->format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { 46 av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); 47 ret = AVERROR_OPTION_NOT_FOUND; 48 break; 49 } 50 51 if (playerState->genpts) { 52 pFormatCtx->flags |= AVFMT_FLAG_GENPTS; 53 } 54 av_format_inject_global_side_data(pFormatCtx); 55 56 opts = setupStreamInfoOptions(pFormatCtx, playerState->codec_opts); 57 58 // 查找媒体流信息 59 ret = avformat_find_stream_info(pFormatCtx, opts); 60 if (opts != NULL) { 61 for (int i = 0; i < pFormatCtx->nb_streams; i++) { 62 if (opts[i] != NULL) { 63 av_dict_free(&opts[i]); 64 } 65 } 66 av_freep(&opts); 67 } 68 69 if (ret < 0) { 70 av_log(NULL, AV_LOG_WARNING, 71 "%s: could not find codec parameters\n", url); 72 ret = -1; 73 break; 74 } 75 76 // 文件时长(秒) 77 if (pFormatCtx->duration != AV_NOPTS_VALUE) { 78 mDuration = (int)(pFormatCtx->duration / AV_TIME_BASE); 79 } 80 81 if (pFormatCtx->pb) { 82 pFormatCtx->pb->eof_reached = 0; 83 } 84 // 判断是否以字节方式定位 85 playerState->seekByBytes = !!(pFormatCtx->iformat->flags & AVFMT_TS_DISCONT) 86 && strcmp("ogg", pFormatCtx->iformat->name); 87 88 // 设置最大帧间隔 89 mediaSync->setMaxDuration((pFormatCtx->iformat->flags & AVFMT_TS_DISCONT) ? 10.0 : 3600.0); 90 91 // 如果不是从头开始播放,则跳转到播放位置 92 if (playerState->startTime != AV_NOPTS_VALUE) { 93 int64_t timestamp; 94 95 timestamp = playerState->startTime; 96 if (pFormatCtx->start_time != AV_NOPTS_VALUE) { 97 timestamp += pFormatCtx->start_time; 98 } 99 ret = avformat_seek_file(pFormatCtx, -1, INT64_MIN, timestamp, INT64_MAX, 0);100 if (ret < 0) {101 av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f\n",102 url, (double)timestamp / AV_TIME_BASE);103 }104 }105 // 判断是否实时流,判断是否需要设置无限缓冲区106 playerState->realTime = isRealTime(pFormatCtx);107 if (playerState->infiniteBuffer < 0 && playerState->realTime) {108 playerState->infiniteBuffer = 1;109 }110111 // 查找媒体流信息112 int audioIndex = -1;113 int videoIndex = -1;114 for (int i = 0; i < pFormatCtx->nb_streams; ++i) {115 if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {116 if (audioIndex == -1) {117 audioIndex = i;118 }119 } else if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {120 if (videoIndex == -1) {121 videoIndex = i;122 }123 }124 }125 // 如果不禁止视频流,则查找最合适的视频流索引126 if (!playerState->videoDisable) {127 videoIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO,128 videoIndex, -1, NULL, 0);129 } else {130 videoIndex = -1;131 }132 // 如果不禁止音频流,则查找最合适的音频流索引(与视频流关联的音频流)133 if (!playerState->audioDisable) {134 audioIndex = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_AUDIO,135 audioIndex, videoIndex, NULL, 0);136 } else {137 audioIndex = -1;138 }139140 // 如果音频流和视频流都没有找到,则直接退出141 if (audioIndex == -1 && videoIndex == -1) {142 av_log(NULL, AV_LOG_WARNING,143 "%s: could not find audio and video stream\n", url);144 ret = -1;145 break;146 }147148 // 根据媒体流索引准备解码器149 if (audioIndex >= 0) {150 prepareDecoder(audioIndex);151 }152 if (videoIndex >= 0) {153 prepareDecoder(videoIndex);154 }155156 if (!audioDecoder && !videoDecoder) {157 av_log(NULL, AV_LOG_WARNING,158 "failed to create audio and video decoder\n");159 ret = -1;160 break;161 }162 ret = 0;163 } while (false);164 mMutex.unlock();165166 // 出错返回167 if (ret < 0) {168 if (playerCallback) {169 playerCallback->onError(0x01, "prepare decoder failed!");170 }171 return -1;172 }173174 // 准备完成回调175 if (playerCallback) {176 playerCallback->onPrepared();177 }178179 // 视频解码器开始解码180 if (videoDecoder != NULL) {181 videoDecoder->start();182 } else {183 if (playerState->syncType == AV_SYNC_VIDEO) {184 playerState->syncType = AV_SYNC_AUDIO;185 }186 }187188 // 音频解码器开始解码189 if (audioDecoder != NULL) {190 audioDecoder->start();191 } else {192 if (playerState->syncType == AV_SYNC_AUDIO) {193 playerState->syncType = AV_SYNC_EXTERNAL;194 }195 }196197 // 打开音频输出设备198 if (audioDecoder != NULL) {199 AVCodecContext *avctx = audioDecoder->getCodecContext();200 ret = openAudioDevice(avctx->channel_layout, avctx->channels,201 avctx->sample_rate);202 if (ret < 0) {203 av_log(NULL, AV_LOG_WARNING, "could not open audio device\n");204 // 如果音频设备打开失败,则调整时钟的同步类型205 if (playerState->syncType == AV_SYNC_AUDIO) {206 if (videoDecoder != NULL) {207 playerState->syncType = AV_SYNC_VIDEO;208 } else {209 playerState->syncType = AV_SYNC_EXTERNAL;210 }211 }212 } else {213 // 启动音频输出设备214 audioDevice->start();215 }216 }217218 if (videoDecoder) {219 if (playerState->syncType == AV_SYNC_AUDIO) {220 videoDecoder->setMasterClock(mediaSync->getAudioClock());221 } else if (playerState->syncType == AV_SYNC_VIDEO) {222 videoDecoder->setMasterClock(mediaSync->getVideoClock());223 } else {224 videoDecoder->setMasterClock(mediaSync->getExternalClock());225 }226 }227228 // 开始同步229 mediaSync->start(videoDecoder, audioDecoder);230231 // 读数据包流程232 eof = 0;233 ret = 0;234 AVPacket pkt1, *pkt = &pkt1;235 int64_t stream_start_time;236 int playInRange = 0;237 int64_t pkt_ts;238 for (;;) {239240 // 退出播放器241 if (playerState->abortRequest) {242 break;243 }244245 // 是否暂停246 if (playerState->pauseRequest != lastPaused) {247 lastPaused = playerState->pauseRequest;248 if (playerState->pauseRequest) {249 av_read_pause(pFormatCtx);250 } else {251 av_read_play(pFormatCtx);252 }253 }254255#if CONFIG_RTSP_DEMUXER || CONFIG_MMSH_PROTOCOL256 if (playerState->pauseRequest &&257 (!strcmp(pFormatCtx->iformat->name, "rtsp") ||258 (pFormatCtx->pb && !strncmp(url, "mmsh:", 5)))) {259 continue;260 }261#endif262 // 定位处理263 if (playerState->seekRequest) {264 int64_t seek_target = playerState->seekPos;265 int64_t seek_min = playerState->seekRel > 0 ? seek_target - playerState->seekRel + 2: INT64_MIN;266 int64_t seek_max = playerState->seekRel < 0 ? seek_target - playerState->seekRel - 2: INT64_MAX;267 // 定位268 ret = avformat_seek_file(pFormatCtx, -1, seek_min, seek_target, seek_max, playerState->seekFlags);269 if (ret < 0) {270 av_log(NULL, AV_LOG_ERROR, "%s: error while seeking\n", url);271 } else {272 if (audioDecoder) {273 audioDecoder->flush();274 }275 if (videoDecoder) {276 videoDecoder->flush();277 }278279 if (audioDevice) {280 audioDevice->flush();281 }282283 // 更新外部时钟值284 if (playerState->seekFlags & AVSEEK_FLAG_BYTE) {285 mediaSync->updateExternalClock(NAN);286 } else {287 mediaSync->updateExternalClock(seek_target / (double)AV_TIME_BASE);288 }289 mediaSync->refreshVideoTimer();290 }291 attachmentRequest = 1;292 playerState->seekRequest = 0;293 eof = 0;294 }295296 // 取得封面数据包297 if (attachmentRequest) {298 if (videoDecoder && (videoDecoder->getStream()->disposition299 & AV_DISPOSITION_ATTACHED_PIC)) {300 AVPacket copy;301 if ((ret = av_copy_packet(©, &videoDecoder->getStream()->attached_pic)) < 0) {302 break;303 }304 videoDecoder->pushPacket(©);305 videoDecoder->pushNullPacket();306 }307 attachmentRequest = 0;308 }309310 // 如果队列中存在足够的数据包,则等待消耗311 // 备注:这里要等待一定时长的缓冲队列,要不然会导致OpenSLES播放音频出现卡顿等现象312 if (playerState->infiniteBuffer < 1 &&313 ((audioDecoder ? audioDecoder->getMemorySize() : 0) + (videoDecoder ? videoDecoder->getMemorySize() : 0) > MAX_QUEUE_SIZE314 || (!audioDecoder || audioDecoder->hasEnoughPackets()) && (!videoDecoder || videoDecoder->hasEnoughPackets()))) {315 continue;316 }317318 // 读出数据包319 ret = av_read_frame(pFormatCtx, pkt);320 if (ret < 0) {321 // 如果没能读出裸数据包,判断是否是结尾322 if ((ret == AVERROR_EOF || avio_feof(pFormatCtx->pb)) && !eof) {323 if (videoDecoder != NULL) {324 videoDecoder->pushNullPacket();325 }326 if (audioDecoder != NULL) {327 audioDecoder->pushNullPacket();328 }329 eof = 1;330 }331 // 读取出错,则直接退出332 if (pFormatCtx->pb && pFormatCtx->pb->error) {333 ret = -1;334 break;335 }336337 // 如果不处于暂停状态,并且队列中所有数据都没有,则判断是否需要338 if (!playerState->pauseRequest && (!audioDecoder || audioDecoder->getPacketSize() == 0)339 && (!videoDecoder || (videoDecoder->getPacketSize() == 0340 && videoDecoder->getFrameSize() == 0))) {341 if (playerState->loop) {342 seekTo(playerState->startTime != AV_NOPTS_VALUE ? playerState->startTime : 0);343 } else if (playerState->autoExit) {344 ret = AVERROR_EOF;345 break;346 }347 }348 continue;349 } else {350 eof = 0;351 }352353 // 计算pkt的pts是否处于播放范围内354 stream_start_time = pFormatCtx->streams[pkt->stream_index]->start_time;355 pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;356 // 播放范围357 playInRange = playerState->duration == AV_NOPTS_VALUE358 || (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *359 av_q2d(pFormatCtx->streams[pkt->stream_index]->time_base)360 - (double)(playerState->startTime != AV_NOPTS_VALUE ? playerState->startTime : 0) / 1000000361 <= ((double)playerState->duration / 1000000);362 if (playInRange && audioDecoder && pkt->stream_index == audioDecoder->getStreamIndex()) {363 audioDecoder->pushPacket(pkt);364 } else if (playInRange && videoDecoder && pkt->stream_index == videoDecoder->getStreamIndex()) {365 videoDecoder->pushPacket(pkt);366 } else {367 av_packet_unref(pkt);368 }369 }370371 if (ret < 0) {372 if (playerCallback) {373 playerCallback->onError(0x02, "error when reading packets!");374 }375 } else { // 播放完成376 if (playerCallback) {377 playerCallback->onComplete();378 }379 }380381 ALOGD("read packets thread exit!");382 return ret;383}
完整代码可以参考作者的播放器项目:
https://github.com/CainKernel/CainPlayer
推荐阅读
关注微信公众号【纸上浅谈】,阅读更多Android开发、音视频、Camera、OpenGL、NDK 开发相关文章~~~
