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)


相关推荐

  • 面向对象——三大基本特征

    面向对象的三大基本特征:封装、继承和多态一、封装利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。优点:减少耦合:可以独立地开发、测试、优化、使用、理解和修改 减轻维护的负担:可以更容易…

  • java byte数组拷贝_异或校验原理

    java byte数组拷贝_异或校验原理其实在以前没接触这些内容的时候,脸上是懵逼的表情,完全不明白异或是为了干什么。其实用简单的语言来说,接收数据的异或校验相当于解密,发送时候的校验位相当于加密;官方解释是:其他数据信息传递中为保证数据传递正确可靠,在数据帧中常加载异或校验位(个人理解怕传输过程中出现数据丢失损坏的情况,所以加校验保证了数据的准确性)言归正传java中怎么异或校验1、发送数据byte[]rece=newbyte[6];rece[0]=0x55;

  • 分布式锁的实现与应用场景对比

    分布式锁的实现与应用场景对比分布式锁在传统的基于数据库的架构中,对于数据的抢占问题往往是通过数据库事务(ACID)来保证的。在分布式环境中,出于对性能以及一致性敏感度的要求,使得分布式锁成为了一种比较常见而高效的解决方案。应用场景介绍:场景1:场景2:某服务提供一组任务,A请求随机从任务组中获取一个任务;B请求随机从任务组中获取一个任务。在理想的情况下,A从任务组中挑选一个任务,任务组删除该任务,B从剩下的的任务中

  • lombok idea插件_idea怎么导入插件

    lombok idea插件_idea怎么导入插件白天试试下eclipse,确实可以节省不少冗余代码,Lombok底层通过asm原理实现的,具体源码还没研究。1、准备工作2、安装插件3、maven集成org.projectlomboklombok-maven-plugin1.16

  • ASP.NET 使用Ajax

    ASP.NET 使用Ajax之前在Ajax初步理解中介绍了对Ajax的初步理解,本文将介绍在ASP.NET中如何方便使用Ajax,第一种当然是使用jQuery的ajax,功能强大而且操作简单方便,第二种是使用.NET封装好的Sc

  • sqlserver 视图创建索引_Oracle创建索引

    sqlserver 视图创建索引_Oracle创建索引一、索引1、添加索引createindex索引对象名on索引对应表名(表内索引对象字段名);例:需创建包含userid属性的userinfo表。createindexuseridonsystem.userinfo(userid);2、删除索引dropindex索引对象名;例:dropindexuserid;二、视图(并不是真实存在的一张表)1、创建视图createview视图名(学号,姓名,科目,成绩)asselect对应在表格中的字段名from涉

发表回复

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

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