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;
    }
  • 相关阅读:
    ASCII码表以及不同进制间的O(1)转换
    四则运算表达式的语法分析
    语法分析器初步学习——LISP语法分析
    ASC与HEX之间的转换
    字节存储数据
    进制之间的转换
    java多线程CountDownLatch
    java反射机制
    java注解
    使用javafx实现视频播放器
  • 原文地址:https://www.cnblogs.com/vczf/p/14077575.html
Copyright © 2011-2022 走看看