ffmplay 源码解读

stream_open 讲解

// 定义一个静态函数用于初始化并返回VideoState结构体指针,用于管理播放状态
static VideoState* stream_open(const char* filename, AVInputFormat* iformat) {
	VideoState* is; // 创建VideoState结构体指针

	// 分配内存并初始化VideoState结构体,若分配失败则直接返回NULL
	is = av_mallocz(sizeof(VideoState));
	if (!is)
		return NULL;

	// 复制输入文件名到VideoState结构体中,失败则跳转到fail标签释放资源
	is->filename = av_strdup(filename);//这是FFmpeg提供的一个便捷函数,用于创建一个字符串的副本,并且自动管理内存分配。它的行为类似于C标准库中的 strdup 函数,但它是FFmpeg专为视频处理等多媒体应用设计的,可能在内部做了某些特定于库的优化或错误处理。该函数接受一个字符串指针作为参数,返回一个新的分配的内存空间中的字符串副本。如果内存分配失败,它将返回 NULL。
	if (!is->filename)
		goto fail;

	// 设置输入格式和视频初始显示位置
	is->iformat = iformat;
	is->ytop = 0;
	is->xleft = 0;

	// 初始化帧队列,分别为视频帧、字幕帧、音频样本队列,失败则跳转到fail
	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;

	// 初始化数据包队列,包括视频、音频、字幕数据包队列,任一失败则跳转到fail
	if (packet_queue_init(&is->videoq) < 0 ||
		packet_queue_init(&is->audioq) < 0 ||
		packet_queue_init(&is->subtitleq) < 0)
		goto fail;

	// 创建条件变量,用于线程同步,失败则记录错误并跳转到fail
	if (!(is->continue_read_thread = SDL_CreateCond())) {
		av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
		goto fail;
	}

	// 初始化时钟,分别与视频、音频和外部时钟序列关联
	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\n", startup_volume);
	if (startup_volume > 100)
		av_log(NULL, AV_LOG_WARNING, "-volume=%d > 100, setting to 100\n", 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;

	// 创建读取线程,负责读取媒体数据,失败则记录错误并跳转到fail
	is->read_tid = SDL_CreateThread(read_thread, "read_thread", is);
	if (!is->read_tid) {
		av_log(NULL, AV_LOG_FATAL, "SDL_CreateThread(): %s\n", SDL_GetError());
		goto fail;
	}

	// 若一切顺利,返回VideoState结构体指针
	return is;

fail:
	// 清理资源并返回NULL
	stream_close(is);
	return NULL;
}

frame_queue_init 讲解

/**
 * 初始化帧队列结构体FrameQueue,包括线程同步对象和帧缓冲区的分配。
 *
 * @param f          FrameQueue结构体指针,待初始化的帧队列对象。
 * @param pktq       PacketQueue结构体指针,关联的包队列,用于接收解码前的数据包。
 * @param max_size   帧队列的最大容量。
 * @param keep_last 是否保留队列中的最后一个帧,即使队列已满。
 * @return 初始化成功返回0,失败返回负的AVERROR代码。
 */
static int frame_queue_init(FrameQueue* f, PacketQueue* pktq, int max_size, int keep_last)
{
    int i; // 用于循环计数

    // 首先,使用memset清除f所指向的内存,确保所有字段初始化为0
    memset(f, 0, sizeof(FrameQueue));

    // 创建互斥锁,用于保护队列访问的线程安全
    if (!(f->mutex = SDL_CreateMutex())) {
        // 如果创建失败,记录致命错误并返回ENOMEM
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }

    // 创建条件变量,用于线程间的同步,例如在队列为空时等待新帧的到来
    if (!(f->cond = SDL_CreateCond())) {
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }

    // 设置队列的关联数据包队列、最大容量和是否保留最后一个帧的标志
    f->pktq = pktq;
    // 确保最大容量不超过预设的最大值FRAME_QUEUE_SIZE
    f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
    f->keep_last = !!keep_last; // 转换为布尔值,确保0或非0逻辑正确

    // 遍历至max_size,为队列中的每个槽位分配AVFrame结构体
    for (i = 0; i < f->max_size; i++) {
        // 分配帧缓冲,用于存储解码后的视频或音频帧
        if (!(f->queue[i].frame = av_frame_alloc())) {
            // 分配失败,返回ENOMEM错误
            return AVERROR(ENOMEM);
        }
    }

    // 所有初始化操作成功完成,返回0
    return 0;
}

packet_queue_init 讲解

/**
 * 初始化数据包队列处理结构体PacketQueue,包括创建必要的线程同步对象。
 *
 * @param q 指向PacketQueue结构体的指针,待初始化的数据包队列对象。
 * @return 初始化成功返回0,失败返回负的AVERROR代码。
 */
static int packet_queue_init(PacketQueue* q)
{
    // 使用memset函数清零q指向的内存区域,确保PacketQueue的所有成员初始化为0
    memset(q, 0, sizeof(PacketQueue));

    // 创建一个互斥锁(mutex),用于保护队列的并发访问,确保线程安全
    q->mutex = SDL_CreateMutex();
    if (!q->mutex) {
        // 如果创建互斥锁失败,记录一个致命错误并返回ENOMEM错误码
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }

    // 创建一个条件变量(cond),用于线程间的同步,例如在队列为空时挂起等待新数据包的到来
    q->cond = SDL_CreateCond();
    if (!q->cond) {
        // 如果创建条件变量失败,同样记录致命错误并返回ENOMEM
        av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
        return AVERROR(ENOMEM);
    }

    // 初始化队列的abort_request标志为1,表明开始时没有请求中止操作
    // (这个标志通常会在队列的生命周期中用来请求停止队列的操作,如用户请求退出)
    q->abort_request = 1;

    // 成功完成所有初始化步骤,返回0
    return 0;
}

read_thread 讲解

/* 这个线程从磁盘或网络获取流数据 */
static int read_thread(void* arg)
{
    // 获取传入的参数,VideoState 结构体
    VideoState* is = arg;
    // AVFormatContext 结构体指针,用于存储媒体文件格式信息
    AVFormatContext* ic = NULL;
    int err, i, ret;
    // 存储流索引的数组,初始化为-1
    int st_index[AVMEDIA_TYPE_NB];
    // AVPacket 用于存储解码前的数据包
    AVPacket pkt1, *pkt = &pkt1;
    int64_t stream_start_time;
    int pkt_in_play_range = 0;
    AVDictionaryEntry* t;
    // SDL 互斥锁,用于线程同步
    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\n", SDL_GetError());
        ret = AVERROR(ENOMEM);
        goto fail;
    }

  
	// 清零 st_index 数组并将其值设为 -1。 st_index 数组通常用于存储流索引。
	memset(st_index, -1, sizeof(st_index));
	
	// 初始化视频、音频和字幕流索引,分别将它们的最后使用和当前使用的流索引设置为 -1。
	is->last_video_stream = is->video_stream = -1;
	is->last_audio_stream = is->audio_stream = -1;
	is->last_subtitle_stream = is->subtitle_stream = -1;
	
	// 初始化 eof 标志,表示流未到达末尾。
	is->eof = 0;


    // 分配 AVFormatContext 结构体
    ic = avformat_alloc_context();
    if (!ic) {
        av_log(NULL, AV_LOG_FATAL, "Could not allocate context.\n");
        ret = AVERROR(ENOMEM);
        goto fail;
    }
    // 设置中断回调函数
    ic->interrupt_callback.callback = decode_interrupt_cb;
    ic->interrupt_callback.opaque = is;
	/*
	这行代码将 AVFormatContext 的 interrupt_callback 结构体中的 callback 成员设置为 decode_interrupt_cb 函数指针。
	decode_interrupt_cb 是一个用户定义的回调函数,用于在特定条件下中断解码过程,例如用户请求停止解码或者出现某些错误条件。
	这个回调函数的签名通常是 int (*callback)(void*),返回值是 int 类型,参数是一个 void* 类型的指针。
	ic->interrupt_callback.opaque = is;:
	
	这行代码将 AVFormatContext 的 interrupt_callback 结构体中的 opaque 成员设置为 is,通常是一个指向用户自定义数据结构的指针。
	opaque 成员将作为参数传递给 decode_interrupt_cb 回调函数,允许回调函数访问特定的上下文数据(如当前的解码状态)。
	*/
	
    // 如果没有设置 scan_all_pmts 选项,则设置它
    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;
    }
    // 打开输入文件
    err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts);
    if (err < 0) {
        print_error(is->filename, err);
        ret = -1;
        goto fail;
    }
    // 如果设置了 scan_all_pmts 选项,则删除它
    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.\n", t->key);
        ret = AVERROR_OPTION_NOT_FOUND;
        goto fail;
    }
    is->ic = ic;

    // 如果设置了 genpts 选项,则设置 AVFormatContext 的标志
    if (genpts)
        ic->flags |= AVFMT_FLAG_GENPTS;

    // 注入全局侧数据
    av_format_inject_global_side_data(ic);

    // 如果设置了 find_stream_info 选项,查找流信息
    if (find_stream_info) {
        AVDictionary** opts = setup_find_stream_info_opts(ic, codec_opts);
        int orig_nb_streams = ic->nb_streams;

        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\n", is->filename);
            ret = -1;
            goto fail;
        }
    }
	/*
	如果 find_stream_info 为真,则调用 setup_find_stream_info_opts 函数设置流信息查找选项,并返回一个 AVDictionary** 类型的指针数组 opts。
	获取原始流数量 orig_nb_streams,即打开文件后的流数量。
	调用 avformat_find_stream_info 函数查找流信息,并传入查找选项 opts。
	在查找完成后,释放 opts 数组中每个元素对应的 AVDictionary 结构体,并释放 opts 数组本身。
	如果查找失败(err < 0),记录警告日志,并设置 ret 为 -1,然后跳转到 fail 标签处执行清理工作。
	这段代码的作用是确保媒体文件的流信息被正确解析和初始化,以便后续的播放或处理操作能够正确进行。
	*/
    // 如果存在 IO 上下文,重置 eof_reached 标志
    if (ic->pb)
        ic->pb->eof_reached = 0; // FIXME hack, ffplay maybe should not use avio_feof() to test for the end
       //检查 ic 的输入上下文是否存在,如果存在,则将 eof_reached 标志设为 0。这个标志用于指示输入流是否已经读取到文件末尾,通过将其设为 0,可以重置该标志,以便后续重新读取文件时能够正确处理文件末尾的情况。

    // 根据文件格式标志设置 seek_by_bytes 选项
    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 (start_time != AV_NOPTS_VALUE) {
        int64_t timestamp;

        timestamp = start_time;
        // 添加流的开始时间
        if (ic->start_time != AV_NOPTS_VALUE)
            timestamp += ic->start_time;
        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\n",
                is->filename, (double)timestamp / AV_TIME_BASE);
        }
    }

    // 判断是否为实时流
    is->realtime = is_realtime(ic);

    // 如果需要显示状态,则打印流信息
    if (show_status)
        av_dump_format(ic, 0, is->filename, 0);

    // 设置流丢弃策略,并选择最佳流
    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;
    }
    /*
    这段代码的目的是为了选择最佳的音频、视频和字幕流,以备后续使用。通过设置丢弃策略为 AVDISCARD_ALL,可以确保不会使用到不需要的流数据。最终,st_index 数组中记录了每种类型流的最佳索引,以便后续使用。
    */
    
    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\n", wanted_stream_spec[i], av_get_media_type_string(i));
            st_index[i] = INT_MAX;
        }
    }

    // 查找最佳流
    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;
    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);//函数设置默认窗口大小,该函数用于设置视频显示窗口的宽度和高度,以及采样宽高比,以确保视频显示正确比例。
    }

    // 打开音频流
    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\n",
            is->filename);
        ret = -1;
        goto fail;
    }

    // 如果是实时流且未设置 infinite_buffer,则将其设为 1
    if (is->realtime)
        infinite_buffer = 1;

    // 读取数据包
    for (;;) {
        // 检查是否需要退出
        if (is->abort_request)
            break;
        // 如果暂停请求已发出且未显示 EOF,则等待暂停
        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 */
            SDL_Delay(10);
            continue;
        }
#endif
        // 如果发生 EOF 或流结束且队列中还有数据,则等待数据消费
        if (is->eof) {
            if (is->videoq.size + is->audioq.size + is->subtitleq.size == 0) {
                if (loop != 1 && (!loop || --loop)) {
                    stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0);
                }
                else if (autoexit) {
                    ret = AVERROR_EOF;
                    goto fail;
                }
            }
            if (is->eof)
                SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            is->eof = 0;
        }

        // 如果 seek 请求已发出,执行 seek 操作
        if (is->seek_req) {
            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;
            // 记录当前播放位置
            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\n", is->ic->url);
            }
            else {
                // 清空队列并重置解码器
                if (is->audio_stream >= 0) {
                    packet_queue_flush(&is->audioq);
                    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);
        }

        // 如果队列已满且流未结束,则等待数据消费
        if (queue_full(&is->videoq) && queue_full(&is->audioq) && queue_full(&is->subtitleq)) {
            /* wait 10 ms to avoid trying to get another packet */
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            continue;
        }

        // 如果队列已满且 stream_end 则等待数据消费
        if (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 to avoid trying to get another packet */
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            continue;
        }
        // 从输入文件读取数据包
        ret = av_read_frame(ic, pkt);
        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;
            /* wait 10 ms to avoid trying to get another packet */
            SDL_CondWaitTimeout(is->continue_read_thread, wait_mutex, 10);
            continue;
        }
        else {
            is->eof = 0;
        }
        // 如果是附件包且请求队列附件,则入队
        if (pkt->stream_index == is->audio_stream ||
            pkt->stream_index == is->video_stream ||
            pkt->stream_index == is->subtitle_stream) {
            if (av_packet_make_refcounted(pkt) < 0)
                continue;
            packet_queue_put(&is->audioq, 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;
}

stream_component_open 讲解

/* 开启指定流(音频、视频或字幕)。返回0表示成功,否则返回错误码 */
static int stream_component_open(VideoState* is, int stream_index)
{
    AVFormatContext* ic = is->ic; // 输入上下文指针
    AVCodecContext* avctx; // 编解码器上下文
    AVCodec* codec; // 编解码器
    const char* forced_codec_name = NULL; // 强制使用的编解码器名字
    AVDictionary* opts = NULL; // 选项字典
    AVDictionaryEntry* t = NULL; // 用于遍历字典条目的指针
    int sample_rate, nb_channels; // 采样率和通道数
    int64_t channel_layout; // 通道布局
    int ret = 0; // 返回值
    int stream_lowres = lowres; // 低分辨率设置

    // 检查流索引有效性
    if (stream_index < 0 || stream_index >= ic->nb_streams)
        return -1;

    // 分配并初始化编解码器上下文
    avctx = avcodec_alloc_context3(NULL);
    if (!avctx)
        return AVERROR(ENOMEM);

    // 将流的编解码参数复制到编解码器上下文中
    ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
    if (ret < 0)
        goto fail;
    avctx->pkt_timebase = ic->streams[stream_index]->time_base; // 设置时间基

    // 查找解码器
    codec = avcodec_find_decoder(avctx->codec_id);

    // 根据流类型选择强制解码器
    switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO: is->last_audio_stream = stream_index; forced_codec_name = audio_codec_name; break;
    case AVMEDIA_TYPE_SUBTITLE: is->last_subtitle_stream = stream_index; forced_codec_name = subtitle_codec_name; break;
    case AVMEDIA_TYPE_VIDEO: is->last_video_stream = stream_index; forced_codec_name = video_codec_name; break;
    }
    // 如果设置了强制解码器名,则使用该解码器
    if (forced_codec_name)
        codec = avcodec_find_decoder_by_name(forced_codec_name);
    if (!codec) {
        // 找不到解码器,打印错误并返回
        if (forced_codec_name)
            av_log(NULL, AV_LOG_WARNING, "找不到指定名字的解码器: '%s'\n", forced_codec_name);
        else
            av_log(NULL, AV_LOG_WARNING, "找不到解码器: %s\n", avcodec_get_name(avctx->codec_id));
        ret = AVERROR(EINVAL);
        goto fail;
    }

    // 设置编解码器ID
    avctx->codec_id = codec->id;
    // 限制低分辨率设置不超过解码器支持的最大值
    if (stream_lowres > codec->max_lowres) {
        av_log(avctx, AV_LOG_WARNING, "解码器支持的最大低分辨率是 %d\n", codec->max_lowres);
        stream_lowres = codec->max_lowres;
    }
    avctx->lowres = stream_lowres;

    // 快速解码标志
    if (fast)
        avctx->flags2 |= AV_CODEC_FLAG2_FAST;

    // 应用编解码器选项
    opts = filter_codec_opts(codec_opts, avctx->codec_id, ic, ic->streams[stream_index], codec);
    // 添加线程选项(如果没有设置则自动)
    if (!av_dict_get(opts, "threads", NULL, 0))
        av_dict_set(&opts, "threads", "auto", 0);
    // 设置低分辨率选项
    if (stream_lowres)
        av_dict_set_int(&opts, "lowres", stream_lowres, 0);
    // 参考帧计数选项
    if (avctx->codec_type == AVMEDIA_TYPE_VIDEO || avctx->codec_type == AVMEDIA_TYPE_AUDIO)
        av_dict_set(&opts, "refcounted_frames", "1", 0);
    // 打开解码器
    if ((ret = avcodec_open2(avctx, codec, &opts)) < 0) {
        goto fail;
    }
    // 检查是否有未使用的选项
    if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
        av_log(NULL, AV_LOG_ERROR, "未找到选项 %s\n", t->key);
        ret = AVERROR_OPTION_NOT_FOUND;
        goto fail;
    }

    // 重置EOF标志
    is->eof = 0;
    // 设置默认丢弃模式
    ic->streams[stream_index]->discard = AVDISCARD_DEFAULT;

switch (avctx->codec_type) {
    case AVMEDIA_TYPE_AUDIO:
#if CONFIG_AVFILTER
        {
            AVFilterContext* sink;

            // 配置音频过滤源参数,根据编解码器上下文设置采样率、通道数、通道布局和样本格式
            is->audio_filter_src.freq = avctx->sample_rate;
            is->audio_filter_src.channels = avctx->channels;
            is->audio_filter_src.channel_layout = get_valid_channel_layout(avctx->channel_layout, avctx->channels);
            is->audio_filter_src.fmt = avctx->sample_fmt;

            // 配置音频过滤器,若失败则跳转到错误处理
            if ((ret = configure_audio_filters(is, afilters, 0)) < 0)
                goto fail;

            // 获取过滤后的sink(输出)参数:采样率、通道数、通道布局
            sink = is->out_audio_filter;
            sample_rate = av_buffersink_get_sample_rate(sink);
            nb_channels = av_buffersink_get_channels(sink);
            channel_layout = av_buffersink_get_channel_layout(sink);
        }
#else
            // 如果没有配置音频过滤器支持,直接使用编解码器的原始参数
            sample_rate = avctx->sample_rate;
            nb_channels = avctx->channels;
            channel_layout = avctx->channel_layout;
#endif

        // 准备音频输出:根据获取的参数初始化音频输出目标格式,失败则跳转到错误处理
        if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
            goto fail;

        // 设置硬件音频缓冲区大小、源缓冲区指针、缓冲区大小和缓冲区索引初始值
        is->audio_hw_buf_size = ret;
        is->audio_src = is->audio_tgt;
        is->audio_buf_size = 0;
        is->audio_buf_index = 0;

        // 初始化音频差异平均滤波器系数和计数器,用于音频同步调整
        is->audio_diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB);
        is->audio_diff_avg_count = 0;
        // 定义音频同步修正阈值,基于硬件缓冲区大小和目标格式的时间基
        is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;

        // 设置音频流索引和流对象
        is->audio_stream = stream_index;
        is->audio_st = ic->streams[stream_index];

        // 初始化音频解码器上下文,绑定音频队列,并指定读取线程的继续函数
        decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);

        // 如果输入格式不允许搜索且不支持随机访问,则设置解码器的起始PTS
        if ((is->ic->iformat->flags & (AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK)) && !is->ic->iformat->read_seek) {
            is->auddec.start_pts = is->audio_st->start_time;
            is->auddec.start_pts_tb = is->audio_st->time_base;
        }

        // 启动音频解码线程,若失败则跳转到错误处理
        if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
            goto out;

        // 恢复音频设备播放
        SDL_PauseAudioDevice(audio_dev, 0);
        break;
    case AVMEDIA_TYPE_VIDEO:
        // 视频流处理:设置视频流索引和流对象,初始化视频解码器上下文并启动解码线程
        is->video_stream = stream_index;
        is->video_st = ic->streams[stream_index];
        decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread);
        if ((ret = decoder_start(&is->viddec, video_thread, "video_decoder", is)) < 0)
            goto out;
        // 标记需要队列附件请求
        is->queue_attachments_req = 1;
        break;
    case AVMEDIA_TYPE_SUBTITLE:
        // 字幕流处理:设置字幕流索引和流对象,初始化字幕解码器上下文并启动解码线程
        is->subtitle_stream = stream_index;
        is->subtitle_st = ic->streams[stream_index];
        decoder_init(&is->subdec, avctx, &is->subtitleq, is->continue_read_thread);
        if ((ret = decoder_start(&is->subdec, subtitle_thread, "subtitle_decoder", is)) < 0)
            goto out;
        break;
    default:
        // 不支持的流类型,直接跳过
        break;
    }

    // 错误处理和清理
fail:
    avcodec_free_context(&avctx);
    av_dict_free(&opts);

    return ret; // 返回结果
}
static int decoder_decode_frame(Decoder* d, AVFrame* frame, AVSubtitle* sub) {
    int ret = AVERROR(EAGAIN); // 初始化返回值为EAGAIN,表示没有可用帧或需要再次尝试

    for (;;) { // 无限循环直到解码成功或遇到错误
        AVPacket pkt; // AVPacket 结构体用于存储视频/音频数据包

        // 检查队列序列号是否与当前解码器序列号匹配
        if (d->queue->serial == d->pkt_serial) {
            do {
                // 检查是否请求了中止解码
                if (d->queue->abort_request)
                    return -1;

                // 根据编解码器类型处理视频或音频帧
                switch (d->avctx->codec_type) {
                case AVMEDIA_TYPE_VIDEO:
                    // 尝试从解码器接收视频帧
                    ret = avcodec_receive_frame(d->avctx, frame);
                    if (ret >= 0) { // 成功解码
                        // 根据配置调整帧的PTS(显示时间戳)
                        if (decoder_reorder_pts == -1) frame->pts = frame->best_effort_timestamp;
                        else if (!decoder_reorder_pts) frame->pts = frame->pkt_dts;
                    }
                    break;
                case AVMEDIA_TYPE_AUDIO:
                    // 尝试从解码器接收音频帧
                    ret = avcodec_receive_frame(d->avctx, frame);
                    if (ret >= 0) {
                        // 调整音频帧的时间戳到正确的时基
                        AVRational tb = (AVRational){1, frame->sample_rate};
                        if (frame->pts != AV_NOPTS_VALUE)
                            frame->pts = av_rescale_q(frame->pts, d->avctx->pkt_timebase, tb);
                        else if (d->next_pts != AV_NOPTS_VALUE)
                            frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
                        if (frame->pts != AV_NOPTS_VALUE) {
                            // 更新下一帧的PTS和时间基
                            d->next_pts = frame->pts + frame->nb_samples;
                            d->next_pts_tb = tb;
                        }
                    }
                    }
                    break;
                }

                // 处理解码器返回的状态
                if (ret == AVERROR_EOF) { // 遇到文件结束
                    d->finished = d->pkt_serial;
                    avcodec_flush_buffers(d->avctx); // 清理解码器缓存
                    return 0; // 正常结束
                }
                if (ret >= 0) // 成功解码一帧
                    return 1;
            } while (ret != AVERROR(EAGAIN)); // 若解码器还需要更多数据则继续
        }

        // 当前队列无匹配的序列号或需要新数据包时
        do {
            // 队列空时通知其他线程可以填充数据
            if (d->queue->nb_packets == 0)
                SDL_CondSignal(d->empty_queue_cond);

            // 使用待处理的数据包
            if (d->packet_pending) {
                av_packet_move_ref(&pkt, &d->pkt);
                d->packet_pending = 0;
            } else {
                // 从队列获取新的数据包
                if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0)
                    return -1;
            }
        } while (d->queue->serial != d->pkt_serial); // 确保序列号匹配

        // 处理数据包
        if (pkt.data == flush_pkt.data) { // 刷新包
            avcodec_flush_buffers(d->avctx); // 清理解码器状态
            d->finished = 0; // 重置结束标记
            d->next_pts = d->start_pts; // 重置PTS
            d->next_pts_tb = d->start_pts_tb;
        } else {
            // 字幕处理
            if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {
                int got_frame = 0;
                ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &pkt);
                if (ret < 0) ret = AVERROR(EAGAIN);
                else {
                    if (got_frame && !pkt.data) {
                        d->packet_pending = 1;
                        av_packet_move_ref(&d->pkt, &pkt);
                    }
                    ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF);
                }
            // 非字幕的常规处理
            } else {
                if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) {
                    av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
                    d->packet_pending = 1;
                    av_packet_move_ref(&d->pkt, &pkt);
                }
            }
            }
            av_packet_unref(&pkt); // 释放pkt引用
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值