ffplay播放器原理剖析

ffplay播放器原理剖析****************************************************************************** ffplay系列博客:                                           ** ffplay播放器原理剖析    …

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

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

* ffplay系列博客:                                                                                      *

ffplay播放器原理剖析                                                                                 *

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

ffplay播放控制代码分析                                                                             *

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

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

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

    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()的主干代码及相关函数代码如下:

  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.   
  30. static int packet_queue_put(PacketQueue *q, AVPacket *pkt)  
  31. {  
  32.     int ret;  
  33.   
  34.     SDL_LockMutex(q->mutex);  // packet queue的写和读操作是在不同线程中,需要加锁互斥访问  
  35.     ret = packet_queue_put_private(q, pkt);  
  36.     SDL_UnlockMutex(q->mutex);  
  37.   
  38.     if (pkt != &flush_pkt && ret < 0)  
  39.         av_packet_unref(pkt);  
  40.   
  41.     return ret;  
  42. }  
  43.   
  44. static int packet_queue_put_private(PacketQueue *q, AVPacket *pkt)  
  45. {  
  46.     // PacketQueue为链表,往链表末尾追加成员  
  47.     MyAVPacketList *pkt1;  
  48.   
  49.     if (q->abort_request)  
  50.        return -1;  
  51.   
  52.     pkt1 = av_malloc(sizeof(MyAVPacketList)); //创建packet  
  53.     if (!pkt1)  
  54.         return -1;  
  55.     pkt1->pkt = *pkt;  
  56.     pkt1->next = NULL;  
  57.     if (pkt == &flush_pkt)  
  58.         q->serial++;  
  59.     pkt1->serial = q->serial;  
  60.   
  61.     if (!q->last_pkt)  
  62.         q->first_pkt = pkt1;  
  63.     else  
  64.         q->last_pkt->next = pkt1; //将新pkt放到PacketQueue链表的末尾  
  65.     q->last_pkt = pkt1; // 更新链表尾巴  
  66.     q->nb_packets++;    // 链表packet个数加1  
  67.     q->size += pkt1->pkt.size + sizeof(*pkt1); //更新链表size  
  68.     q->duration += pkt1->pkt.duration;  //更新链表所有packets的总duration  
  69.     /* XXX: should duplicate packet data in DV case */  
  70.     SDL_CondSignal(q->cond); //通知packet_queue_get()函数,链表中有新packet了。  
  71.     return 0;  
  72. }  

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()的主干代码及相关代码如下:

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

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()的主干代码及相关代码如下:

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

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

主线程视频渲染解析

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 基于HTTP重定向的GSLB工作流程「建议收藏」

    基于HTTP重定向的GSLB工作流程「建议收藏」1.用户向网站的localdns请求域名解析2.localdns向用户返回GSLB设备的ip地址,如果GSLB系统本身有负载均衡,则返回的IP地址就是自身负载均衡后的某台设备的IP地址3.用户向这台GSLB设备发起GET请求,请求该网站的网页的内容.如果网页上有图片,文字,视频则会发起多个HTTP请求4.GSLB设备将综合分析用户ip,内容分布,设备负载,链路状况等实时…

  • mysql死锁的处理方法_避免数据库死锁

    mysql死锁的处理方法_避免数据库死锁怎么避免mysql死锁

  • 什么是关联数据[通俗易懂]

    什么是关联数据[通俗易懂]维基百科的定义在计算机领域,关联数据描述了一种发布结构化数据的方法,使得数据能够相互连接起来,便于更好的使用。中文权威期刊的定义关联数据是国际互联网协会(W3C)推荐的一种规范,用来发布和连接各类数据信息和知识。

  • 服务器centos6.5安装教程_服务器是什么系统

    服务器centos6.5安装教程_服务器是什么系统操作系统下载地址:https://pan.baidu.com/s/17Vcx81m_ZnGmxnHFlMrvog密码:v7b3安装完成NeoKylin操作系统之后进行虚拟网卡静态IP配置虚拟化环境搭建:Vmware或Virtuabox1.1. 虚拟机网络模式VMnet0表示的是用于桥接模式下的虚拟交换机;VMnet1表示的是用于仅主机模式下的虚拟交换机;VMnet8表示的是用于NAT模式下的虚拟交换机。综述:VMware安装成功之后

  • 宏基因组数据分析:差异分析(LEfSe安装使用及LDA score计算)

    宏基因组数据分析:差异分析(LEfSe安装使用及LDA score计算)文章目录简介安装简介安装报错:Collectingpackagemetadata(current_repodata.json):doneSolvingenvironment:failedwithinitialfrozensolve.Retryingwithflexiblesolve.Solvingenvironment:failedwithrepodatafromcurrent_repodata.json,willretrywithnextre

  • Android调用系统原生分享组件[通俗易懂]

    Android调用系统原生分享组件[通俗易懂]想必做Android开发都会遇到的需求——分享。当然实现的需求和方式的也都各自不一,有接入某个app的SDK进行分享,也有集成第三方平台的例如友盟等等…接下来所要说到的是Android系统提供的分享组件。分享组件能够自动的检索到可以分享app然后将分享内容带入 当然这个也会有所限制的,会有个别app只能分享单一项:“文字+图片”、“图片”、“文字” 好处就是轻量级、避免导入其它jar包或依赖、可减少apk体积Filefile=newFile(filePath

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号