zoukankan      html  css  js  c++  java
  • ffplay源码分析02 ---- 数据读取线程

    =====================================================

    ffplay源码分析01 ---- 框架

    ffplay源码分析02 ---- 数据读取线程

    ffplay源码分析03 ---- 视频解码线程

    ffplay源码分析03 ---- 音频解码线程

    ffplay源码分析04 ---- 音频输出

    ffplay源码分析05 ---- 音频重采样

    ffplay源码分析06 ---- 视频输出

    ffplay源码分析07 ---- 音视频同步

    =====================================================

    1.stream_open() 
    static VideoState *stream_open(const char *filename, AVInputFormat *iformat)
    {
        // 数据初始化
        VideoState *is;
    
        is = av_mallocz(sizeof(VideoState));    /* 分配结构体并初始化 */
        if (!is)
            return NULL;
        is->filename = av_strdup(filename);
        if (!is->filename)
            goto fail;
        is->iformat = iformat;
        is->ytop    = 0;
        is->xleft   = 0;
    
        /* 初始化帧队列 */
        if (frame_queue_init(&is->pictq, &is->videoq, VIDEO_PICTURE_QUEUE_SIZE, 1) < 0)
            goto fail;
        if (frame_queue_init(&is->subpq, &is->subtitleq, SUBPICTURE_QUEUE_SIZE, 0) < 0)
            goto fail;
        if (frame_queue_init(&is->sampq, &is->audioq, SAMPLE_QUEUE_SIZE, 1) < 0)
            goto fail;
    
        if (packet_queue_init(&is->videoq) < 0 ||
            packet_queue_init(&is->audioq) < 0 ||
            packet_queue_init(&is->subtitleq) < 0)
            goto fail;
    
        /* 创建 */
        if (!(is->continue_read_thread = SDL_CreateCond())) {
            av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s
    ", SDL_GetError());
            goto fail;
        }
    
        /*
         * 初始化时钟
         * 时钟序列->queue_serial,实际上指向的是is->videoq.serial
         */
        init_clock(&is->vidclk, &is->videoq.serial);
        init_clock(&is->audclk, &is->audioq.serial);
        init_clock(&is->extclk, &is->extclk.serial);
        is->audio_clock_serial = -1;
         /* 初始化音量 */
        if (startup_volume < 0)
            av_log(NULL, AV_LOG_WARNING, "-volume=%d < 0, setting to 0
    ", startup_volume);
        if (startup_volume > 100)
            av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100
    ", startup_volume);
        startup_volume = av_clip(startup_volume, 0, 100);
        startup_volume = av_clip(SDL_MIX_MAXVOLUME * startup_volume / 100, 0, SDL_MIX_MAXVOLUME);
        is->audio_volume = startup_volume;
        is->muted = 0;
        // 同步类型:默认以音频为基准
        is->av_sync_type = av_sync_type;
        /* 创建读线程 */
        is->read_tid     = SDL_CreateThread(read_thread, "read_thread", is);
        if (!is->read_tid) {
            av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s
    ", SDL_GetError());
    fail:
            stream_close(is);
            return NULL;
        }
        return is;
    }

    这个比较简单,就是一些数据初始化

     2. read_thread()

    /* this thread gets the stream from the disk or the network */
    /*
     * 数据都由这里读取
     * 主要功能是做解复用,从码流中分离音视频packet,并插入缓存队列
     */
    static int read_thread(void *arg)
    {
        VideoState *is = arg;
        AVFormatContext *ic = NULL;
        int err, i, ret;
        int st_index[AVMEDIA_TYPE_NB];
        AVPacket pkt1, *pkt = &pkt1;
        int64_t stream_start_time;
        int pkt_in_play_range = 0;
        AVDictionaryEntry *t;
        SDL_mutex *wait_mutex = SDL_CreateMutex();
        int scan_all_pmts_set = 0;
        int64_t pkt_ts;
        // 一、准备流程
        if (!wait_mutex) {
            av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s
    ", SDL_GetError());
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    
        memset(st_index, -1, sizeof(st_index));
        // 初始化为-1,如果一直为-1说明没相应steam
        is->last_video_stream = is->video_stream = -1;
        is->last_audio_stream = is->audio_stream = -1;
        is->last_subtitle_stream = is->subtitle_stream = -1;
        is->eof = 0;    // =1是表明数据读取完毕
        // 1. 创建上下文结构体,这个结构体是最上层的结构体,表示输入上下文
        ic = avformat_alloc_context();
        if (!ic) {
            av_log(NULL, AV_LOG_FATAL, "Could not allocate context.
    ");
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        /* 2.设置中断回调函数,如果出错或者退出,就根据目前程序设置的状态选择继续check或者直接退出 */
        /* 当执行耗时操作时(一般是在执行while或者for循环的数据读取时),会调用interrupt_callback.callback
         * 回调函数中返回1则代表ffmpeg结束耗时操作退出当前函数的调用
         * 回调函数中返回0则代表ffmpeg内部继续执行耗时操作,直到完成既定的任务(比如读取到既定的数据包)
         */
        ic->interrupt_callback.callback = decode_interrupt_cb;
        ic->interrupt_callback.opaque = is;
        //特定选项处理
        if (!av_dict_get(format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE)) {
            av_dict_set(&format_opts, "scan_all_pmts", "1", AV_DICT_DONT_OVERWRITE);
            scan_all_pmts_set = 1;
        }
         /* 3.打开文件,主要是探测协议类型,如果是网络文件则创建网络链接等 */
        err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
        if (err < 0) {
            print_error(is->filename, err);
            ret = -1;
            goto fail;
        }
        if (scan_all_pmts_set)
            av_dict_set(&format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);
    
        if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
            av_log(NULL, AV_LOG_ERROR, "Option %s not found.
    ", t->key);
            ret = AVERROR_OPTION_NOT_FOUND;
            goto fail;
        }
        is->ic = ic;    // videoState的ic指向分配的ic
    
        if (genpts)
            ic->flags |= AVFMT_FLAG_GENPTS;
    
        av_format_inject_global_side_data(ic);
    
        if (find_stream_info) {
            AVDictionary **opts = setup_find_stream_info_opts(ic, codec_opts);
            int orig_nb_streams = ic->nb_streams;
    
             /*
             * 4.探测媒体类型,可得到当前文件的封装格式,音视频编码参数等信息
             * 调用该函数后得多的参数信息会比只调用avformat_open_input更为详细,
             * 其本质上是去做了decdoe packet获取信息的工作
             * codecpar, filled by libavformat on stream creation or
             * in avformat_find_stream_info()
             */
            err = avformat_find_stream_info(ic, opts);
    
            for (i = 0; i < orig_nb_streams; i++)
                av_dict_free(&opts[i]);
            av_freep(&opts);
    
            if (err < 0) {
                av_log(NULL, AV_LOG_WARNING,
                       "%s: could not find codec parameters
    ", is->filename);
                ret = -1;
                goto fail;
            }
        }
    
        if (ic->pb)
            ic->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use avio_feof() to test for the end
    
        if (seek_by_bytes < 0)
            seek_by_bytes = !!(ic->iformat->flags & AVFMT_TS_DISCONT) && strcmp("ogg", ic->iformat->name);
    
        is->max_frame_duration = (ic->iformat->flags & AVFMT_TS_DISCONT) ? 10.0 : 3600.0;
    
        if (!window_title && (t = av_dict_get(ic->metadata, "title", NULL, 0)))
            window_title = av_asprintf("%s - %s", t->value, input_filename);
    
        /* if seeking requested, we execute it */
        /* 5. 检测是否指定播放起始时间 */
        if (start_time != AV_NOPTS_VALUE) {
            int64_t timestamp;
    
            timestamp = start_time;
            /* add the stream start time */
            if (ic->start_time != AV_NOPTS_VALUE)
                timestamp += ic->start_time;
            // seek的指定的位置开始播放
            ret = avformat_seek_file(ic, -1, INT64_MIN, timestamp, INT64_MAX, 0);
            if (ret < 0) {
                av_log(NULL, AV_LOG_WARNING, "%s: could not seek to position %0.3f
    ",
                        is->filename, (double)timestamp / AV_TIME_BASE);
            }
        }
    
        /* 是否为实时流媒体 */
        is->realtime = is_realtime(ic);
    
        if (show_status)
            av_dump_format(ic, 0, is->filename, 0);
    
        // 6. 查找AVStream
        // 6.1 根据用户指定来查找流,
        for (i = 0; i < ic->nb_streams; i++) {
            AVStream *st = ic->streams[i];
            enum AVMediaType type = st->codecpar->codec_type;
            st->discard = AVDISCARD_ALL;
            if (type >= 0 && wanted_stream_spec[type] && st_index[type] == -1)
                if (avformat_match_stream_specifier(ic, st, wanted_stream_spec[type]) > 0)
                    st_index[type] = i;
        }
        for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
            if (wanted_stream_spec[i] && st_index[i] == -1) {
                av_log(NULL, AV_LOG_ERROR, "Stream specifier %s does not match any %s stream
    ",
                       wanted_stream_spec[i], av_get_media_type_string(i));
    //            st_index[i] = INT_MAX;
                st_index[i] = -1;
            }
        }
        // 6.2 利用av_find_best_stream选择流,
        if (!video_disable)
            st_index[AVMEDIA_TYPE_VIDEO] =
                av_find_best_stream(ic, AVMEDIA_TYPE_VIDEO,
                                    st_index[AVMEDIA_TYPE_VIDEO], -1, NULL, 0);
        if (!audio_disable)
            st_index[AVMEDIA_TYPE_AUDIO] =
                av_find_best_stream(ic, AVMEDIA_TYPE_AUDIO,
                                    st_index[AVMEDIA_TYPE_AUDIO],
                                    st_index[AVMEDIA_TYPE_VIDEO],
                                    NULL, 0);
        if (!video_disable && !subtitle_disable)
            st_index[AVMEDIA_TYPE_SUBTITLE] =
                av_find_best_stream(ic, AVMEDIA_TYPE_SUBTITLE,
                                    st_index[AVMEDIA_TYPE_SUBTITLE],
                                    (st_index[AVMEDIA_TYPE_AUDIO] >= 0 ?
                                     st_index[AVMEDIA_TYPE_AUDIO] :
                                     st_index[AVMEDIA_TYPE_VIDEO]),
                                    NULL, 0);
    
        is->show_mode = show_mode;
        //7 从待处理流中获取相关参数,设置显示窗口的宽度、高度及宽高比
        if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) {
            AVStream *st = ic->streams[st_index[AVMEDIA_TYPE_VIDEO]];
            AVCodecParameters *codecpar = st->codecpar;
            /*根据流和帧宽高比猜测帧的样本宽高比。该值只是一个参考
            */
            AVRational sar = av_guess_sample_aspect_ratio(ic, st, NULL);
            if (codecpar->width) {
                // 设置显示窗口的大小和宽高比
                set_default_window_size(codecpar->width, codecpar->height, sar);
            }
        }
    
        /* open the streams */
        /* 8. 打开视频、音频解码器。在此会打开相应解码器,并创建相应的解码线程。 */
        if (st_index[AVMEDIA_TYPE_AUDIO] >= 0) {// 如果有音频流则打开音频流
            stream_component_open(is, st_index[AVMEDIA_TYPE_AUDIO]);
        }
    
        ret = -1;
        if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { // 如果有视频流则打开视频流
            ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]);
        }
        if (is->show_mode == SHOW_MODE_NONE) {
            //选择怎么显示,如果视频打开成功,就显示视频画面,否则,显示音频对应的频谱图
            is->show_mode = ret >= 0 ? SHOW_MODE_VIDEO : SHOW_MODE_RDFT;
        }
    
        if (st_index[AVMEDIA_TYPE_SUBTITLE] >= 0) { // 如果有字幕流则打开字幕流
            stream_component_open(is, st_index[AVMEDIA_TYPE_SUBTITLE]);
        }
    
        if (is->video_stream < 0 && is->audio_stream < 0) {
            av_log(NULL, AV_LOG_FATAL, "Failed to open file '%s' or configure filtergraph
    ",
                   is->filename);
            ret = -1;
            goto fail;
        }
    
        if (infinite_buffer < 0 && is->realtime)
            infinite_buffer = 1;    // 如果是实时流
    
        /*
         * 二、For循环流程
        */
        for (;;) {
            // 1 检测是否退出
            if (is->abort_request)
                break;
            // 2 检测是否暂停/继续
            if (is->paused != is->last_paused) {
                is->last_paused = is->paused;
                if (is->paused)
                    is->read_pause_return = av_read_pause(ic); // 网络流的时候有用
                else
                    av_read_play(ic);
            }
    #if CONFIG_RTSP_DEMUXER || CONFIG_MMSH_PROTOCOL
            if (is->paused &&
                    (!strcmp(ic->iformat->name, "rtsp") ||
                     (ic->pb && !strncmp(input_filename, "mmsh:", 5)))) {
                /* wait 10 ms to avoid trying to get another packet */
                /* XXX: horrible */
                // 等待10ms,避免立马尝试下一个Packet
                SDL_Delay(10);
                continue;
            }
    #endif
            //  3 检测是否seek
            if (is->seek_req) { // 是否有seek请求
                int64_t seek_target = is->seek_pos;
                int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
                int64_t seek_max    = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
        // FIXME the +-2 is due to rounding being not done in the correct direction in generation
        //      of the seek_pos/seek_rel variables
                // 修复由于四舍五入,没有再seek_pos/seek_rel变量的正确方向上进行
                ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
                if (ret < 0) {
                    av_log(NULL, AV_LOG_ERROR,
                           "%s: error while seeking
    ", is->ic->url);
                } else {
                    /* seek的时候,要把原先的数据情况,并重启解码器,put flush_pkt的目的是告知解码线程需要
                     * reset decoder
                     */
                    if (is->audio_stream >= 0) { // 如果有音频流
                        packet_queue_flush(&is->audioq);    // 清空packet队列数据
                        // 放入flush pkt, 用来开起新的一个播放序列, 解码器读取到flush_pkt也清空解码器
                        packet_queue_put(&is->audioq, &flush_pkt);
                    }
                    if (is->subtitle_stream >= 0) { // 如果有字幕流
                        packet_queue_flush(&is->subtitleq); // 和上同理
                        packet_queue_put(&is->subtitleq, &flush_pkt);
                    }
                    if (is->video_stream >= 0) {    // 如果有视频流
                        packet_queue_flush(&is->videoq);    // 和上同理
                        packet_queue_put(&is->videoq, &flush_pkt);
                    }
                    if (is->seek_flags & AVSEEK_FLAG_BYTE) {
                       set_clock(&is->extclk, NAN, 0);
                    } else {
                       set_clock(&is->extclk, seek_target / (double)AV_TIME_BASE, 0);
                    }
                }
                is->seek_req = 0;
                is->queue_attachments_req = 1;
                is->eof = 0;
                if (is->paused)
                    step_to_next_frame(is);
            }
            // 4 检测video是否为attached_pic
            if (is->queue_attachments_req) {
                // attached_pic 附带的图片。比如说一些MP3,AAC音频文件附带的专辑封面,所以需要注意的是音频文件不一定只存在音频流本身
                if (is->video_st && is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
                    AVPacket copy = { 0 };
                    if ((ret = av_packet_ref(&copy, &is->video_st->attached_pic)) < 0)
                        goto fail;
                    packet_queue_put(&is->videoq, &copy);
                    packet_queue_put_nullpacket(&is->videoq, is->video_stream);
                }
                is->queue_attachments_req = 0;
            }
            // 5 检测队列是否已经有足够数据
            /* if the queue are full, no need to read more */
            /* 缓存队列有足够的包,不需要继续读取数据 15M * 8 / 3(Mbps) = 40秒*/
            if (infinite_buffer<1 &&
                  (is->audioq.size + is->videoq.size + is->subtitleq.size > MAX_QUEUE_SIZE
                || (stream_has_enough_packets(is->audio_st, is->audio_stream, &is->audioq) &&
                    stream_has_enough_packets(is->video_st, is->video_stream, &is->videoq) &&
                    stream_has_enough_packets(is->subtitle_st, is->subtitle_stream, &is->subtitleq)))) {
                /* wait 10 ms */
                SDL_LockMutex(wait_mutex);
                // 如果没有唤醒则超时10ms退出,比如在seek操作时这里会被唤醒
                SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
                SDL_UnlockMutex(wait_mutex);
                continue;
            }
            // 6 检测码流是否已经播放结束
            if (!is->paused // 非暂停
                    && // 这里的执行是因为码流读取完毕后 插入空包所致
                    (!is->audio_st // 没有音频流
                        || (is->auddec.finished == is->audioq.serial // 或者音频播放完毕
                                && frame_queue_nb_remaining(&is->sampq) == 0))
                    && (!is->video_st // 没有视频流
                        || (is->viddec.finished == is->videoq.serial // 或者视频播放完毕
                                && frame_queue_nb_remaining(&is->pictq) == 0))) {
                if (loop != 1           // a 是否循环播放
                        && (!loop || --loop)) {
                    // stream_seek不是ffmpeg的函数,是ffplay封装的,每次seek的时候会调用
                    stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0);
                } else if (autoexit) {  // b 是否自动退出
                    ret = AVERROR_EOF;
                    goto fail;
                }
            }
    
            // 7.读取媒体数据,得到的是音视频分离后、解码前的数据
            ret = av_read_frame(ic, pkt); // 调用不会释放pkt的数据,需要我们自己去释放packet的数据
    
            // 8 检测数据是否读取完毕
            if (ret < 0) {
                if ((ret == AVERROR_EOF || avio_feof(ic->pb))
                        && !is->eof)
                {
                    // 插入空包说明码流数据读取完毕了,之前讲解码的时候说过刷空包是为了从解码器把所有帧都读出来
                    if (is->video_stream >= 0)
                        packet_queue_put_nullpacket(&is->videoq, is->video_stream);
                    if (is->audio_stream >= 0)
                        packet_queue_put_nullpacket(&is->audioq, is->audio_stream);
                    if (is->subtitle_stream >= 0)
                        packet_queue_put_nullpacket(&is->subtitleq, is->subtitle_stream);
                    is->eof = 1;        // 文件读取完毕
                }
                if (ic->pb && ic->pb->error)
                    break;
                SDL_LockMutex(wait_mutex);
                SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
                SDL_UnlockMutex(wait_mutex);
                continue;        // 继续循环
            } else {
                is->eof = 0;
            }
            // 9 检测是否在播放范围内
            /* check if packet is in play range specified by user, then queue, otherwise discard */
            stream_start_time = ic->streams[pkt->stream_index]->start_time; // 获取流的起始时间
            pkt_ts = pkt->pts == AV_NOPTS_VALUE ? pkt->dts : pkt->pts; // 获取packet的时间戳
            // 这里的duration是在命令行时用来指定播放长度
            pkt_in_play_range = duration == AV_NOPTS_VALUE ||
                    (pkt_ts - (stream_start_time != AV_NOPTS_VALUE ? stream_start_time : 0)) *
                    av_q2d(ic->streams[pkt->stream_index]->time_base) -
                    (double)(start_time != AV_NOPTS_VALUE ? start_time : 0) / 1000000
                    <= ((double)duration / 1000000);
            // 10 将音视频数据分别送入相应的queue中
            if (pkt->stream_index == is->audio_stream && pkt_in_play_range) {
                packet_queue_put(&is->audioq, pkt);
            } else if (pkt->stream_index == is->video_stream && pkt_in_play_range
                       && !(is->video_st->disposition & AV_DISPOSITION_ATTACHED_PIC)) {
                //printf("pkt pts:%ld, dts:%ld
    ", pkt->pts, pkt->dts);
                packet_queue_put(&is->videoq, pkt);
            } else if (pkt->stream_index == is->subtitle_stream && pkt_in_play_range) {
                packet_queue_put(&is->subtitleq, pkt);
            } else {
                av_packet_unref(pkt);// // 不入队列则直接释放数据
            }
        }
    
        // 三 退出线程处理
        ret = 0;
     fail:
        if (ic && !is->ic)
            avformat_close_input(&ic);
    
        if (ret != 0) {
            SDL_Event event;
    
            event.type = FF_QUIT_EVENT;
            event.user.data1 = is;
            SDL_PushEvent(&event);
        }
        SDL_DestroyMutex(wait_mutex);
        return 0;
    }
  • 相关阅读:
    Dot Net WinForm 控件开发 (七) 为属性提下拉式属性编辑器
    WinForm 程序的界面多语言切换
    c#遍历HashTable
    Dot Net WinForm 控件开发 (三) 自定义类型的属性需要自定义类型转换器
    Dot Net WinForm 控件开发 (六) 为属性提供弹出式编辑对话框
    Dot Net WinForm 控件开发 (一) 写一个最简单的控件
    Dot Net WinForm 控件开发 (四) 设置属性的默认值
    Dot Net WinForm 控件开发 (二) 给控件来点描述信息
    Dot Net WinForm 控件开发 (八) 调试控件的设计时行为
    Dot Net WinForm 控件开发 (五) 复杂属性的子属性
  • 原文地址:https://www.cnblogs.com/vczf/p/14077575.html
Copyright © 2011-2022 走看看