前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ffplay播放器原理剖析

ffplay播放器原理剖析

作者头像
全栈程序员站长
发布2022-09-07 15:30:12
7140
发布2022-09-07 15:30:12
举报
文章被收录于专栏:全栈程序员必看

大家好,又见面了,我是你们的朋友全栈君。

*****************************************************************************

* ffplay系列博客: *

* ffplay播放器原理剖析 *

* ffplay播放器音视频同步原理 *

* ffplay播放控制代码分析 *

* 视频主观质量对比工具(Visual comparision tool based on ffplay) *

*****************************************************************************

ffplay是使用ffmpeg api开发的功能完善的开源播放器,弄懂ffplay原理可以帮助我们很好的理解播放器的工作机制,但是目前很少看到关于ffplay的系统介绍的文章,所以下面基于ffmpeg-3.1.1的源代码来剖析ffplay的工作机制。

播放器框架

首先,一个简单的通用播放器的基本框架图如下

ffplay播放器原理剖析
ffplay播放器原理剖析

ffplay的总体框架解读

在ffplay中,各个线程角色如下:

read_thread()线程扮演着图中Demuxer的角色。

video_thread()线程扮演着图中Video Decoder的角色。

audio_thread()线程扮演着图中Audio Decoder的角色。

主线程中的event_loop()函数循环调用refresh_loop_wait_event()则扮演着图中 视频渲染的角色。

回调函数sdl_audio_callback扮演图中音频播放的角色。VideoState结构体变量则扮演者各个线程之间的信使。

因此ffplay的基本框架图如下:

ffplay播放器原理剖析
ffplay播放器原理剖析

1、read_thread线程负责读取文件内容,将video和audio内容分离出来生成packet,将packet输出到packet队列中,包括Video Packet Queue和Audio Packet Queue(不考虑subtitle)。

2、video_thread线程负责读取Video Packets Queue队列,将video packet解码得到Video Frame,将Video Frame输出到Video Frame Queue队列中。

3、audio_thread线程负责读取Audio Packets Queue队列,将audio packet解码得到Audio Frame,将Audio Frame输出到Audio Frame Queue队列中。

4、主函数(主线程)->event_loop()->refresh_loop_wait_event()负责读取Video Frame Queue中的video frame,调用SDL进行显示(其中包括了音视频同步控制的相关操作)。

5、SDL的回调函数sdl_audio_callback()负责读取Audio Frame Queue中的audio frame,对其进行处理后,将数据返回给SDL,然后SDL进行音频播放。

ffplay数据的流通

研究数据的流通可以帮助理解播放器的工作机制。ffplay中有两个关键队列packet queue和frame queue,把往队列中添加成员的操作称之为生产,从队列中取走成员的操作称之为消耗。通过分析各个队列的生产和消耗可以帮助我们弄明白数据是如何流通的。

Packet Queue,Video(Audio) Frame Queue的生产和消耗

read_thread()解析

read_thread()负责packet queue的生产,包括Video Packet Queue(is->videoq)和Audio Packet Queue(is->audioq)的生产。

调用关系:read_thread() -> packet_queue_put() -> packet_queue_put_private()

read_thread()的主干代码及相关函数代码如下:

[cpp] view plain copy

  1. static int read_thread(void *arg)
  2. {
  3. VideoState *is = arg;
  4. ……
  5. for (;;) {
  6. ret = av_read_frame(ic, pkt); // 从源文件中读取内容到pkt结构中
  7. /* check if packet is in play range specified by user, then queue, otherwise discard */
  8. stream_start_time = ic->streams[pkt->stream_index]->start_time;
  9. // 下面的duration是通过命令传递给ffplay的指定播放时长的参数,所以判断pkt的时间戳是否在duration内
  10. pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts;
  11. pkt_in_play_range = duration == AV_NOPTS_VALUE ||
  12. (pkt_ts – (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
  13. av_q2d(ic->streams[pkt->stream_index]->time_base) –
  14. (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
  15. <= ((double)duration / 1000000);
  16. if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
  17. packet_queue_put(&is->audioq, pkt); // 读到的pkt为audio,放入audio queue(is->audioq)
  18. } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
  19. && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
  20. packet_queue_put(&is->videoq, pkt); // 读到的pkt为video,放入video queue(is->videoq)
  21. } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
  22. packet_queue_put(&is->subtitleq, pkt); // 读到的pkt为subtitle,放到subtitile queue中
  23. } else {
  24. av_packet_unref(pkt);
  25. }
  26. }
  27. ……
  28. }
  29. static int packet_queue_put(PacketQueue *q, AVPacket *pkt)
  30. {
  31. int ret;
  32. SDL_LockMutex(q->mutex); // packet queue的写和读操作是在不同线程中,需要加锁互斥访问
  33. ret = packet_queue_put_private(q, pkt);
  34. SDL_UnlockMutex(q->mutex);
  35. if (pkt != &flush_pkt && ret < 0)
  36. av_packet_unref(pkt);
  37. return ret;
  38. }
  39. static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)
  40. {
  41. // PacketQueue为链表,往链表末尾追加成员
  42. MyAVPacketList *pkt1;
  43. if (q->abort_request)
  44. return -1;
  45. pkt1 = av_malloc(sizeof(MyAVPacketList)); //创建packet
  46. if (!pkt1)
  47. return -1;
  48. pkt1->pkt = *pkt;
  49. pkt1->next = NULL;
  50. if (pkt == &flush_pkt)
  51. q->serial++;
  52. pkt1->serial = q->serial;
  53. if (!q->last_pkt)
  54. q->first_pkt = pkt1;
  55. else
  56. q->last_pkt->next = pkt1; //将新pkt放到PacketQueue链表的末尾
  57. q->last_pkt = pkt1; // 更新链表尾巴
  58. q->nb_packets++; // 链表packet个数加1
  59. q->size += pkt1->pkt.size + sizeof(*pkt1); //更新链表size
  60. q->duration += pkt1->pkt.duration; //更新链表所有packets的总duration
  61. /* XXX: should duplicate packet data in DV case */
  62. SDL_CondSignal(q->cond); //通知packet_queue_get()函数,链表中有新packet了。
  63. return 0;
  64. }

video_thread()解析:

video_thread()负责Video Packet Queue(is->videoq)的消耗和Video Frame Queue(is->pictq)的生产。

Video Packet Queue消耗的函数调用关系:video_thread() -> get_video_frame() -> packet_queue_get(&is->videoq)

Video Frame Queue生产的函数调用关系:video_thread() -> queue_picture() -> frame_queue_push(&is->pictq) video_thread()的主干代码及相关代码如下:

[cpp] view plain copy

  1. static int video_thread(void *arg)
  2. {
  3. ……
  4. for (;;) {
  5. ret = get_video_frame(is, frame, &pkt, &serial); // 获取解码图像frame
  6. ……
  7. ret = queue_picture(is, frame, pts, duration, av_frame_get_pkt_pos(frame), serial); //将解码图像放到队列Video Frame Queue中
  8. }
  9. …….
  10. }
  11. // 获取解码图像frame
  12. static int get_video_frame(VideoState *is, AVFrame *frame, AVPacket *pkt, int *serial)
  13. {
  14. int got_picture;
  15. if (packet_queue_get(&is->videoq, pkt, 1, serial) < 0) //从队列中取packet
  16. return -1;
  17. ……
  18. if(avcodec_decode_video2(is->video_st->codec, frame, &got_picture, pkt) < 0) //解码packet,获取解码图像frame
  19. return 0;
  20. ……
  21. }
  22. // packet_queue_get用来获取链表packet queue的第一个packet,与packet_queue_put_private对应,一个读一个写
  23. static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
  24. {
  25. MyAVPacketList *pkt1;
  26. int ret;
  27. SDL_LockMutex(q->mutex); // packet queue的写和读操作是在不同线程中,需要加锁互斥访问
  28. for (;;) {
  29. if (q->abort_request) {
  30. ret = -1;
  31. break;
  32. }
  33. pkt1 = q->first_pkt; //获取链表的第一个packet(链表头)
  34. if (pkt1) {
  35. q->first_pkt = pkt1->next; //packet queue链表头更新,指向第二个元素
  36. if (!q->first_pkt)
  37. q->last_pkt = NULL;
  38. q->nb_packets–;
  39. q->size -= pkt1->pkt.size + sizeof(*pkt1);
  40. *pkt = pkt1->pkt; //返回链表第一个packet的内容
  41. if (serial)
  42. *serial = pkt1->serial;
  43. av_free(pkt1);
  44. ret = 1;
  45. break;
  46. } else if (!block) { // packet queue链表是空的,非阻塞条件下,直接返回ret=0
  47. ret = 0;
  48. break;
  49. } else {
  50. SDL_CondWait(q->cond, q->mutex); // packet queue链表是空的(阻塞条件下),则等待read_thread读取packet
  51. }
  52. }
  53. SDL_UnlockMutex(q->mutex);
  54. return ret;
  55. }
  56. // queue_picture函数将解码出来的Video Frame(src_frame)追加到队列Video Frame Queue的末尾。
  57. static int queue_picture(VideoState *is, AVFrame *src_frame, double pts, double duration, int64_t pos, int serial)
  58. {
  59. Frame *vp;
  60. // 探测video frame queue是否有空的Frame,如果有则返回空的Frame(vp),接下来将src_frame中的视频内容拷贝到其中
  61. if (!(vp = frame_queue_peek_writable(&is->pictq)))
  62. return -1;
  63. vp->sar = src_frame->sample_aspect_ratio;
  64. /* alloc or resize hardware picture buffer */
  65. // 是否需要给vp->bmp分配? vp->bmp是一个SDL_Overlay,存放yuv数据,然后在SDL_surface上进行显示
  66. if (!vp->bmp || vp->reallocate || !vp->allocated ||
  67. vp->width != src_frame->width ||
  68. vp->height != src_frame->height) {
  69. SDL_Event event;
  70. vp->allocated = 0;
  71. vp->reallocate = 0;
  72. vp->width = src_frame->width;
  73. vp->height = src_frame->height;
  74. /* the allocation must be done in the main thread to avoid
  75. locking problems. */
  76. event.type = FF_ALLOC_EVENT;
  77. event.user.data1 = is;
  78. SDL_PushEvent(&event); // 在主线程的event_loop函数中,收到FF_ALLOC_EVENT事件后,会调用alloc_picture创建vp->bmp
  79. /* wait until the picture is allocated */
  80. SDL_LockMutex(is->pictq.mutex);
  81. while (!vp->allocated && !is->videoq.abort_request) {
  82. SDL_CondWait(is->pictq.cond, is->pictq.mutex);
  83. }
  84. /* if the queue is aborted, we have to pop the pending ALLOC event or wait for the allocation to complete */
  85. if (is->videoq.abort_request && SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_EVENTMASK(FF_ALLOC_EVENT)) != 1) {
  86. while (!vp->allocated && !is->abort_request) {
  87. SDL_CondWait(is->pictq.cond, is->pictq.mutex);
  88. }
  89. }
  90. SDL_UnlockMutex(is->pictq.mutex);
  91. if (is->videoq.abort_request)
  92. return -1;
  93. }
  94. /* if the frame is not skipped, then display it */
  95. // 将解码出来的yuv数据拷贝到vp->bmp中,供后续显示线程显示
  96. if (vp->bmp) {
  97. uint8_t *data[4];
  98. int linesize[4];
  99. /* get a pointer on the bitmap */
  100. SDL_LockYUVOverlay (vp->bmp); //对SDL_Overlay操作前,先Lock
  101. data[0] = vp->bmp->pixels[0];
  102. data[1] = vp->bmp->pixels[2];
  103. data[2] = vp->bmp->pixels[1];
  104. linesize[0] = vp->bmp->pitches[0];
  105. linesize[1] = vp->bmp->pitches[2];
  106. linesize[2] = vp->bmp->pitches[1];
  107. #if CONFIG_AVFILTER
  108. // FIXME use direct rendering
  109. // 将解码出来的yuv数据拷贝到vp->bmp中,供后续显示线程显示
  110. av_image_copy(data, linesize, (const uint8_t **)src_frame->data, src_frame->linesize,
  111. src_frame->format, vp->width, vp->height);
  112. #else
  113. {
  114. AVDictionaryEntry *e = av_dict_get(sws_dict, “sws_flags”, NULL, 0);
  115. if (e) {
  116. const AVClass *class = sws_get_class();
  117. const AVOption *o = av_opt_find(&class, “sws_flags”, NULL, 0,
  118. AV_OPT_SEARCH_FAKE_OBJ);
  119. int ret = av_opt_eval_flags(&class, o, e->value, &sws_flags);
  120. if (ret < 0)
  121. exit(1);
  122. }
  123. }
  124. is->img_convert_ctx = sws_getCachedContext(is->img_convert_ctx,
  125. vp->width, vp->height, src_frame->format, vp->width, vp->height,
  126. AV_PIX_FMT_YUV420P, sws_flags, NULL, NULL, NULL);
  127. if (!is->img_convert_ctx) {
  128. av_log(NULL, AV_LOG_FATAL, “Cannot initialize the conversion context\n”);
  129. exit(1);
  130. }
  131. sws_scale(is->img_convert_ctx, src_frame->data, src_frame->linesize,
  132. 0, vp->height, data, linesize);
  133. #endif
  134. /* workaround SDL PITCH_WORKAROUND */
  135. duplicate_right_border_pixels(vp->bmp);
  136. /* update the bitmap content */
  137. SDL_UnlockYUVOverlay(vp->bmp); //对SDL_Overlay操作完成,UnLock
  138. vp->pts = pts;
  139. vp->duration = duration;
  140. vp->pos = pos;
  141. vp->serial = serial;
  142. /* now we can update the picture count */
  143. frame_queue_push(&is->pictq); //更新frame queue队列,增加了新的一帧Frame用于显示
  144. }
  145. return 0;
  146. }

audio_thread()解析

audio_thread()负责Audio Packet Queue(is->audioq)的消耗和Audio Sample Queue(is->sampq)的生产。

Audio Packet Queue消耗的函数调用关系:audio_thread() -> decoder_decode_frame() -> packet_queue_get(is->audioq)

Audio Sample Queue生产的函数调用关系:audio_thread() -> frame_queue_push(&is->sampq)。

audio_thread()的主干代码及相关代码如下:

[cpp] view plain copy

  1. static int audio_thread(void *arg)
  2. {
  3. VideoState *is = arg;
  4. AVFrame *frame = av_frame_alloc();
  5. Frame *af;
  6. ……
  7. do {
  8. // 解码音频packet,解码后的音频数据放在frame中
  9. if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
  10. goto the_end;
  11. if (got_frame) {
  12. ……
  13. #if CONFIG_AVFILTER
  14. ……
  15. while ((ret = av_buffersink_get_frame_flags(is->out_audio_filter, frame, 0)) >= 0) {
  16. tb = is->out_audio_filter->inputs[0]->time_base;
  17. #endif
  18. //探测audio sample queue是否有空的Frame,如果有则返回空的Frame(af),接下来将解码的音频sample内容拷贝到其中
  19. if (!(af = frame_queue_peek_writable(&is->sampq)))
  20. goto the_end;
  21. af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
  22. af->pos = av_frame_get_pkt_pos(frame);
  23. af->serial = is->auddec.pkt_serial;
  24. af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
  25. av_frame_move_ref(af->frame, frame); //将解码后的音频数据放到audio sample queue(af)中,后续提供给音频播放设备播放
  26. frame_queue_push(&is->sampq); //更新frame queue队列,增加了新的audio sample用于后续播放
  27. #if CONFIG_AVFILTER
  28. if (is->audioq.serial != is->auddec.pkt_serial)
  29. break;
  30. }
  31. if (ret == AVERROR_EOF)
  32. is->auddec.finished = is->auddec.pkt_serial;
  33. #endif
  34. }
  35. } while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
  36. the_end:
  37. #if CONFIG_AVFILTER
  38. avfilter_graph_free(&is->agraph);
  39. #endif
  40. av_frame_free(&frame);
  41. return ret;
  42. }

音频回调函数sdl_audio_callback()解析

ffplay中,音频的播放采用回调函数的方式,具体来说在打开音频设备时指定回调函数,之后SDL将根据需要不断调用该函数来获取音频数据进行播放。

sdl_audio_callback()负责Audio Sample Queue(is->audioq)的消耗,函数调用:sdl_audio_callback() -> audio_decode_frame() -> frame_queue_peek_readable()

sdl_audio_callback()相关代码如下:

[cpp] view plain copy

  1. static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
  2. {
  3. VideoState *is = opaque;
  4. int audio_size, len1;
  5. audio_callback_time = av_gettime_relative();
  6. while (len > 0) {
  7. if (is->audio_buf_index >= is->audio_buf_size) {
  8. // audio_decode_frame将Audio Sample Queue中的解码后音频数据进行转换,然后存储到is->audio_buf中。
  9. audio_size = audio_decode_frame(is);
  10. if (audio_size < 0) {
  11. /* if error, just output silence */
  12. is->audio_buf = NULL;
  13. is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size * is->audio_tgt.frame_size;
  14. } else {
  15. if (is->show_mode != SHOW_MODE_VIDEO)
  16. update_sample_display(is, (int16_t *)is->audio_buf, audio_size);
  17. is->audio_buf_size = audio_size;
  18. }
  19. is->audio_buf_index = 0;
  20. }
  21. len1 = is->audio_buf_size – is->audio_buf_index;
  22. if (len1 > len)
  23. len1 = len;
  24. // 将音频数据拷贝到stream中,供SDL进行播放使用
  25. if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
  26. memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
  27. else {
  28. memset(stream, 0, len1);
  29. if (!is->muted && is->audio_buf)
  30. SDL_MixAudio(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1, is->audio_volume);
  31. }
  32. len -= len1;
  33. stream += len1;
  34. is->audio_buf_index += len1;
  35. }
  36. is->audio_write_buf_size = is->audio_buf_size – is->audio_buf_index;
  37. /* Let’s assume the audio driver that is used by SDL has two periods. */
  38. if (!isnan(is->audio_clock)) { // 更新audio clock,用于音视频同步
  39. set_clock_at(&is->audclk, is->audio_clock – (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial, audio_callback_time / 1000000.0);
  40. sync_clock_to_slave(&is->extclk, &is->audclk);
  41. }
  42. }
  43. static int audio_decode_frame(VideoState *is)
  44. {
  45. int data_size, resampled_data_size;
  46. int64_t dec_channel_layout;
  47. av_unused double audio_clock0;
  48. int wanted_nb_samples;
  49. Frame *af;
  50. if (is->paused)
  51. return -1;
  52. do {
  53. … …
  54. //探测audio sample queue是否有可读的Frame,如果有则返回该Frame(af),
  55. if (!(af = frame_queue_peek_readable(&is->sampq)))
  56. return -1;
  57. frame_queue_next(&is->sampq);
  58. } while (af->serial != is->audioq.serial);
  59. data_size = av_samples_get_buffer_size(NULL, av_frame_get_channels(af->frame),
  60. af->frame->nb_samples,
  61. af->frame->format, 1);
  62. dec_channel_layout =
  63. (af->frame->channel_layout && av_frame_get_channels(af->frame) == av_get_channel_layout_nb_channels(af->frame->channel_layout)) ?
  64. af->frame->channel_layout : av_get_default_channel_layout(av_frame_get_channels(af->frame));
  65. wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);
  66. if (af->frame->format != is->audio_src.fmt ||
  67. dec_channel_layout != is->audio_src.channel_layout ||
  68. af->frame->sample_rate != is->audio_src.freq ||
  69. (wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx)) {
  70. swr_free(&is->swr_ctx);
  71. is->swr_ctx = swr_alloc_set_opts(NULL,
  72. is->audio_tgt.channel_layout, is->audio_tgt.fmt, is->audio_tgt.freq,
  73. dec_channel_layout, af->frame->format, af->frame->sample_rate,
  74. 0, NULL);
  75. if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {
  76. av_log(NULL, AV_LOG_ERROR,
  77. “Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n”,
  78. af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), av_frame_get_channels(af->frame),
  79. is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);
  80. swr_free(&is->swr_ctx);
  81. return -1;
  82. }
  83. is->audio_src.channel_layout = dec_channel_layout;
  84. is->audio_src.channels = av_frame_get_channels(af->frame);
  85. is->audio_src.freq = af->frame->sample_rate;
  86. is->audio_src.fmt = af->frame->format;
  87. }
  88. if (is->swr_ctx) { //对解码后音频数据转换,符合目标输出格式
  89. const uint8_t **in = (const uint8_t **)af->frame->extended_data;
  90. uint8_t **out = &is->audio_buf1;
  91. int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate + 256;
  92. int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels, out_count, is->audio_tgt.fmt, 0);
  93. int len2;
  94. if (out_size < 0) {
  95. av_log(NULL, AV_LOG_ERROR, “av_samples_get_buffer_size() failed\n”);
  96. return -1;
  97. }
  98. if (wanted_nb_samples != af->frame->nb_samples) {
  99. if (swr_set_compensation(is->swr_ctx, (wanted_nb_samples – af->frame->nb_samples) * is->audio_tgt.freq / af->frame->sample_rate,
  100. wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate) < 0) {
  101. av_log(NULL, AV_LOG_ERROR, “swr_set_compensation() failed\n”);
  102. return -1;
  103. }
  104. }
  105. av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);
  106. if (!is->audio_buf1)
  107. return AVERROR(ENOMEM);
  108. len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);
  109. if (len2 < 0) {
  110. av_log(NULL, AV_LOG_ERROR, “swr_convert() failed\n”);
  111. return -1;
  112. }
  113. if (len2 == out_count) {
  114. av_log(NULL, AV_LOG_WARNING, “audio buffer is probably too small\n”);
  115. if (swr_init(is->swr_ctx) < 0)
  116. swr_free(&is->swr_ctx);
  117. }
  118. is->audio_buf = is->audio_buf1;
  119. resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);
  120. } else {
  121. is->audio_buf = af->frame->data[0]; //audio_buf指向解码后音频数据
  122. resampled_data_size = data_size;
  123. }
  124. audio_clock0 = is->audio_clock;
  125. /* update the audio clock with the pts */
  126. if (!isnan(af->pts))
  127. is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;
  128. else
  129. is->audio_clock = NAN;
  130. is->audio_clock_serial = af->serial;
  131. return resampled_data_size;
  132. }

主线程视频渲染解析

event_loop()->refresh_loop_wait_event() 负责Video Frame Queue的消耗,将Video Frame渲染显示。

调用关系:main() -> event_loop() -> refresh_loop_wait_event()

相关代码如下:

[cpp] view plain copy

  1. static void event_loop(VideoState *cur_stream)
  2. {
  3. SDL_Event event;
  4. double incr, pos, frac;
  5. for (;;) {
  6. ……
  7. refresh_loop_wait_event(cur_stream, &event);
  8. switch (event.type) {
  9. case SDL_KEYDOWN:
  10. ……
  11. }
  12. }
  13. }
  14. static void refresh_loop_wait_event(VideoState *is, SDL_Event *event) {
  15. double remaining_time = 0.0;
  16. // 调用SDL_PeepEvents前先调用SDL_PumpEvents,将输入设备的事件抽到事件队列中
  17. SDL_PumpEvents();
  18. while (!SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_ALLEVENTS)) {
  19. // 从事件队列中拿一个事件,放到event中,如果没有事件,则进入循环中
  20. if (!cursor_hidden && av_gettime_relative() – cursor_last_shown > CURSOR_HIDE_DELAY) {
  21. SDL_ShowCursor(0); //隐藏鼠标
  22. cursor_hidden = 1;
  23. }
  24. if (remaining_time > 0.0) // 在video_refresh函数中,根据当前帧显示时刻和实际时刻计算需要sleep的时间,保证帧按时显示
  25. av_usleep((int64_t)(remaining_time * 1000000.0));
  26. remaining_time = REFRESH_RATE;
  27. if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
  28. video_refresh(is, &remaining_time);
  29. SDL_PumpEvents();
  30. }
  31. }
  32. /* called to display each frame */
  33. static void video_refresh(void *opaque, double *remaining_time)
  34. {
  35. VideoState *is = opaque;
  36. double time;
  37. Frame *sp, *sp2;
  38. if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
  39. check_external_clock_speed(is);
  40. ……
  41. if (is->video_st) {
  42. retry:
  43. if (frame_queue_nb_remaining(&is->pictq) == 0) {
  44. // nothing to do, no picture to display in the queue
  45. } else {
  46. double last_duration, duration, delay;
  47. Frame *vp, *lastvp;
  48. /* dequeue the picture */
  49. lastvp = frame_queue_peek_last(&is->pictq); //取Video Frame Queue上一帧图像
  50. vp = frame_queue_peek(&is->pictq); //取Video Frame Queue当前帧图像
  51. ……
  52. if (is->paused)
  53. goto display;
  54. /* compute nominal last_duration */
  55. last_duration = vp_duration(is, lastvp, vp); //计算两帧之间的时间间隔
  56. delay = compute_target_delay(last_duration, is); //计算当前帧与上一帧渲染的时间差
  57. time= av_gettime_relative()/1000000.0;
  58. //is->frame_timer + delay是当前帧渲染的时刻,如果当前时间还没到帧渲染的时刻,那就要sleep了
  59. if (time < is->frame_timer + delay) { // remaining_time为需要sleep的时间
  60. *remaining_time = FFMIN(is->frame_timer + delay – time, *remaining_time);
  61. goto display;
  62. }
  63. is->frame_timer += delay;
  64. if (delay > 0 && time – is->frame_timer > AV_SYNC_THRESHOLD_MAX)
  65. is->frame_timer = time;
  66. SDL_LockMutex(is->pictq.mutex);
  67. if (!isnan(vp->pts))
  68. update_video_pts(is, vp->pts, vp->pos, vp->serial);
  69. SDL_UnlockMutex(is->pictq.mutex);
  70. if (frame_queue_nb_remaining(&is->pictq) > 1) {
  71. Frame *nextvp = frame_queue_peek_next(&is->pictq);
  72. duration = vp_duration(is, vp, nextvp);
  73. // 如果当前帧显示时刻早于实际时刻,说明解码慢了,帧到的晚了,需要丢弃不能用于显示了,不然音视频不同步了。
  74. if(!is->step && (framedrop>0 || (framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration){
  75. is->frame_drops_late++;
  76. frame_queue_next(&is->pictq);
  77. goto retry;
  78. }
  79. }
  80. ……
  81. frame_queue_next(&is->pictq);
  82. is->force_refresh = 1; //显示当前帧
  83. if (is->step && !is->paused)
  84. stream_toggle_pause(is);
  85. }
  86. display:
  87. /* display picture */
  88. if (!display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
  89. video_display(is);
  90. }
  91. is->force_refresh = 0;
  92. ……
  93. }
  94. static void video_display(VideoState *is)
  95. {
  96. if (!screen)
  97. video_open(is, 0, NULL);
  98. if (is->audio_st && is->show_mode != SHOW_MODE_VIDEO)
  99. video_audio_display(is);
  100. else if (is->video_st)
  101. video_image_display(is);
  102. }
  103. static void video_image_display(VideoState *is)
  104. {
  105. Frame *vp;
  106. Frame *sp;
  107. SDL_Rect rect;
  108. int i;
  109. vp = frame_queue_peek_last(&is->pictq);
  110. if (vp->bmp) {
  111. ……
  112. calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp->width, vp->height, vp->sar);
  113. SDL_DisplayYUVOverlay(vp->bmp, &rect); //显示当前帧
  114. ……
  115. }
  116. }

至此,ffplay正常播放流程基本解析完成了。后面有空,会继续分析ffplay的音视频同步机制、事件响应机制等等。

转载地址: https://blog.csdn.net/dssxk/article/details/50403018

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/148324.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 播放器框架
  • ffplay的总体框架解读
  • ffplay数据的流通
    • Packet Queue,Video(Audio) Frame Queue的生产和消耗
      • read_thread()解析
      • video_thread()解析:
      • audio_thread()解析
      • 音频回调函数sdl_audio_callback()解析
      • 主线程视频渲染解析
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档