当前位置: 首页 > news >正文

慈溪做网站公司/怎么做网站

慈溪做网站公司,怎么做网站,做曖視頻网站,宁波建网站选哪家好一点stream_open 讲解 // 定义一个静态函数用于初始化并返回VideoState结构体指针,用于管理播放状态 static VideoState* stream_open(const char* filename, AVInputFormat* iformat) {VideoState* is; // 创建VideoState结构体指针// 分配内存并初始化VideoState结构…

stream_open 讲解

// 定义一个静态函数用于初始化并返回VideoState结构体指针,用于管理播放状态
static VideoState* stream_open(const char* filename, AVInputFormat* iformat) {VideoState* is; // 创建VideoState结构体指针// 分配内存并初始化VideoState结构体,若分配失败则直接返回NULLis = 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;// 初始化帧队列,分别为视频帧、字幕帧、音频样本队列,失败则跳转到failif (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;// 初始化数据包队列,包括视频、音频、字幕数据包队列,任一失败则跳转到failif (packet_queue_init(&is->videoq) < 0 ||packet_queue_init(&is->audioq) < 0 ||packet_queue_init(&is->subtitleq) < 0)goto fail;// 创建条件变量,用于线程同步,失败则记录错误并跳转到failif (!(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;// 创建读取线程,负责读取媒体数据,失败则记录错误并跳转到failis->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:// 清理资源并返回NULLstream_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所指向的内存,确保所有字段初始化为0memset(f, 0, sizeof(FrameQueue));// 创建互斥锁,用于保护队列访问的线程安全if (!(f->mutex = SDL_CreateMutex())) {// 如果创建失败,记录致命错误并返回ENOMEMav_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_SIZEf->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);}}// 所有初始化操作成功完成,返回0return 0;
}

packet_queue_init 讲解

/*** 初始化数据包队列处理结构体PacketQueue,包括创建必要的线程同步对象。** @param q 指向PacketQueue结构体的指针,待初始化的数据包队列对象。* @return 初始化成功返回0,失败返回负的AVERROR代码。*/
static int packet_queue_init(PacketQueue* q)
{// 使用memset函数清零q指向的内存区域,确保PacketQueue的所有成员初始化为0memset(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) {// 如果创建条件变量失败,同样记录致命错误并返回ENOMEMav_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());return AVERROR(ENOMEM);}// 初始化队列的abort_request标志为1,表明开始时没有请求中止操作// (这个标志通常会在队列的生命周期中用来请求停止队列的操作,如用户请求退出)q->abort_request = 1;// 成功完成所有初始化步骤,返回0return 0;
}

read_thread 讲解

/* 这个线程从磁盘或网络获取流数据 */
static int read_thread(void* arg)
{// 获取传入的参数,VideoState 结构体VideoState* is = arg;// AVFormatContext 结构体指针,用于存储媒体文件格式信息AVFormatContext* ic = NULL;int err, i, ret;// 存储流索引的数组,初始化为-1int 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,则将其设为 1if (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);elseav_read_play(ic);}#if CONFIG_RTSP_DEMUXER || CONFIG_MMSH_PROTOCOLif (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);elseav_log(NULL, AV_LOG_WARNING, "找不到解码器: %s\n", avcodec_get_name(avctx->codec_id));ret = AVERROR(EINVAL);goto fail;}// 设置编解码器IDavctx->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);// 如果输入格式不允许搜索且不支持随机访问,则设置解码器的起始PTSif ((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; // 重置PTSd->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引用}}
}

相关文章:

ffmplay 源码解读

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

java web如何调用py脚本文件

Controller public class IndexController {RequestMapping("/pythonTest")ResponseBodypublic String pythonTest(){// 假设你的Python脚本名为script.pyString pythonScriptPath "D:\\project\\c1\\hello.py";ProcessBuilder processBuilder new Proce…...

K8s:无状态

无状态服务 无状态服务是指服务的实例之间没有持久化状态&#xff0c;每个实例都是相同的&#xff0c;可以互换使用。 调度器 ReplicationController 简称 RC是 Kubernetes 早期版本中用来确保 Pod 副本始终运行的 API 对象。它通过监控 Pod 副本的数量&#xff0c;确保任何…...

Docker 入门篇(九)-- 使用 Maven 插件 构建 Docker 镜像

在这篇教程中&#xff0c;我们将学习如何使用 Maven 插件为 Spring Boot 应用构建 Docker 镜像。我们将使用 spring-boot-maven-plugin 和 dockerfile-maven-plugin 这两个插件。 一、前提条件 已安装 Docker。已安装 JDK 8 或以上版本。已安装 Maven。 二 创建一个 Spring …...

网络协议三

数据中心 一、DNS 现在网站的数目非常多&#xff0c;常用的网站就有二三十个&#xff0c;如果全部用 IP 地址进行访问&#xff0c;恐怕很难记住 根 DNS 服务器 &#xff1a;返回顶级域 DNS 服务器的 IP 地址 顶级域 DNS 服务器&#xff1a;返回权威 DNS 服务器的 IP 地址 …...

LeetCode LRU缓存

题目描述 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;…...

Parallels Desktop for Mac 19.4.0更新了哪些内容?有什么改进?

带来了重新设计的共享 Mac 文件夹版本&#xff0c;这些文件夹现在是符号链接&#xff0c;像指针一样指向您的 Mac 文件夹中的文件&#xff0c;同时仍然显示在 Windows 的本地磁盘上。 修复了由于共享文件夹问题导致 NinjaTrader 无法正常启动的问题。 修复了由于共享文件夹问…...

Python 将CSV文件转为PDF文件

CSV文件通常用于存储大量的数据&#xff0c;而PDF文件则是一种通用的文档格式&#xff0c;便于与他人共享和打印。将CSV文件转换成PDF文件可以帮助我们更好地管理和展示数据。本文将介绍如何通过Python编程将CSV文件导出为PDF文件。 Python Excel库安装及介绍 在 Python 中&am…...

4_XMR交易过程

XMR交易过程 参考文档 书: 《精通门罗币 : 私密交易的未来》(Mastering Monero) 书中的代码示例: 《精通门罗币 : 私密交易的未来》深入探究门罗币与密码学门罗币的环签名分析官方介绍视频 1.隐匿地址 Stealth Address_Monero官方介绍视频2.环签名 Ring Signature_Monero官方…...

02_共享锁和排他锁

共享锁和排他锁 文章目录 共享锁和排他锁简介共享锁&#xff08;Shared Lock, S Lock&#xff09;简介原理使用方式加锁流程使用场景 排他锁&#xff08;Exclusive Lock, X Lock&#xff09;简介原理使用方式加锁流程使用场景 对比注意事项结论 简介 MySQL 中的共享锁和排他锁…...

Ubuntu的启动过程

尽管通常情况下Ubuntu的启动并不需要用户过多地参与&#xff0c;但是Ubuntu系统的启动本身是一个非常复杂的过程。在这个过程中&#xff0c;有硬件的检测、系统内核的准备以及各种系统服务的启动等。作为系统管理员&#xff0c;需要深入了解其中所经历的阶段&#xff0c;才能在…...

c# 下 ScintillaNET 显示XML信息并折叠节点

winform下显示XML信息&#xff08;非WPF&#xff09; 之前使用的是FastColoredTextBox&#xff0c;github地址如下&#xff1a; https://github.com/PavelTorgashov/FastColoredTextBox 但是有个问题&#xff0c;它支持中文&#xff0c;wordwraptrue&#xff0c;自动换行时&…...

什么叫防御式编程

防御式编程是一种编程策略&#xff0c;主要目的是提高代码的健壮性和可靠性。它假设任何错误都可能发生&#xff0c;并且在设计和编写代码时采取预防措施以防止这些错误导致程序崩溃或产生错误结果。 以下是一些防御式编程的常见实践&#xff1a; 输入验证&#xff1a;总是验证…...

前端优化之图片压缩——tinyPNG

今天前端前辈新介绍的一个压缩图片的工具——tinyPNG&#xff0c;地址&#xff1a;TinyPNG – Compress WebP, PNG and JPEG images intelligently可以将图片压缩&#xff0c;进行优化。 一、使用方法——手动压缩 将超过200kb的图片拖到我标注的红框框里&#xff0c;拖到这里…...

Springboot集成Quartz

Quartz简介 Job 表示一个工作&#xff0c;要执行的具体业务内容。 JobDetail 表示一个具体的可执行的调度程序&#xff0c;Job 是这个可执行程调度程序所要执行的内容&#xff0c;另外 JobDetail 还包含了这个任务调度的方案和策略。 Trigger 代表一个调度参数的配置&#xf…...

Android面试题之Kotlin Jetpack组件LifecycleScope

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 在Kotlin中&#xff0c;LifecycleScope是Android Jetpack架构组件的一部分&#xff0c;主要用于简化与生命周期相关的协程管理。 它属于android…...

MySQL深分页优化

MySQL中的深分页问题通常是指当我们通过LIMIT语句查询数据&#xff0c;尤其是在翻到较后面的页码时&#xff0c;性能会急剧下降。例如&#xff0c;查询第1000页的数据&#xff0c;每页10条&#xff0c;系统需要跳过前9990条数据&#xff0c;然后才能获取到所需的记录&#xff0…...

问题:律师会见委托人的方式包括团体会见和( )。 #职场发展#笔记#学习方法

问题&#xff1a;律师会见委托人的方式包括团体会见和&#xff08; &#xff09;。 参考答案如图所示...

Spring Boot中整合Jasypt 使用自定义注解+AOP实现敏感字段的加解密

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…...

pytorch中的维度变换操作性质大总结:view, reshape, transpose, permute

在深度学习中&#xff0c;张量的维度变换是很重要的操作。在pytorch中&#xff0c;有四个用于维度变换的函数&#xff0c;view, reshape, transpose, permute。其中view, reshape都用于改变张量的形状&#xff0c;transpose, permute都用于重新排列张量的维度&#xff0c;但它们…...

LeetCode刷题 | Day 4 分割等和子集(Partition Equal Subset Sum)自底向上动态规划

LeetCode刷题 | Day 4 分割等和子集(Partition Equal Subset Sum)自底向上动态规划 文章目录 LeetCode刷题 | Day 4 分割等和子集(Partition Equal Subset Sum)自底向上动态规划前言一、题目概述二、解题方法2.1 一维表格的自底向上动态规划2.1.1 思路讲解2.1.2 伪代码 + 逐…...

基于工业互联网打造敏捷供应链的实现方式:创新路径与实践应用

引言 工业互联网和敏捷供应链是当今制造业发展中的两个重要概念。工业互联网以数字化、网络化和智能化为核心&#xff0c;致力于将传统工业生产与互联网技术相融合&#xff0c;从而实现生产过程的高效、智能和灵活。而敏捷供应链则强调快速响应市场需求、灵活调整生产和供应计划…...

碳化硅柱式膜的广泛应用

碳化硅柱式膜是一种高性能的过滤材料&#xff0c;以其独特的性质和广泛的应用领域在现代工业中占据着重要地位。以下是对碳化硅柱式膜的详细介绍&#xff1a; 一、基本概述 碳化硅柱式膜是以碳化硅超滤膜为过滤单元构成的&#xff0c;其过滤精度高达0.1微米。这种膜材料具有耐化…...

【QT】QFont字体设置

设置字体大小 f.setPointSize(12); // 设置字体大小为12点设置字体加粗 f.setBold(true); // 使字体加粗设置字体斜体 f.setItalic(true); // 使字体斜体设置字体下划线 f.setUnderline(true); // 给字体添加下划线设置字体删除线 f.setStrikeOut(true); // 给字体添加删除…...

Vue3+vite部署nginx的二级目录,使用hash模式

修改router访问路径 import { createRouter, createWebHashHistory } from vue-routerconst router createRouter({history: createWebHashHistory (/mall4pc-bbc/),routes: [XXX,] })配置package.json文件 "build:testTwo": "vite build --mode testing --ba…...

云南区块链商户平台发票助手成品

目录 1 概述2 功能对比3 项目演示图4 核心逻辑4.1智能赋码4.2 解密方法4.3 登录与检测4.4 发票金额大写转换4.5 检查登录是否失效4.6 验证码识别5 演示效果6 项目部署6.1 Web站点部署6.1.1 环境6.1.2 前端6.1.3 后端6.2 Docker部署6.2.1 构建镜像6.2.2 创建容器6.3.3 访问项目域…...

AI图书推荐:检索增强生成RAG赋能大语言模型

本书《检索增强生成RAG赋能大型语言模型》&#xff08;Retrieval-Augmented Generation - Dr. Ray Islam &#xff1a;Mohammad Rubyet &#xff09;深入探讨了如何通过结合检索系统与神经语言模型&#xff0c;提升人工智能在自然语言处理领域的能力。 以下是各章节内容的概要&…...

高效学习LabVIEW的方法

学习LabVIEW可以通过系统化课程、在线资源、自学实验、参与论坛、结合实际项目等多角度进行。系统课程提供全面基础&#xff0c;在线资源便于查漏补缺&#xff0c;自学实验强化理解&#xff0c;论坛互动解决疑难&#xff0c;结合实际项目应用提高实践技能。结合项目学习是最高效…...

C语言 | Leetcode C语言题解之第136题只出现一次的数字

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> singleNumbers(vector<int>& nums) {int eor 0;for (int num:nums)eor ^ num;int rightOne eor & (~eor 1); // 提取出最右的1int onlyOne 0;for (int cur : nums) {if ((cur…...

如何利用Varjo混合现实技术改变飞机维修训练方式

自2017年以来&#xff0c;总部位于休斯顿的HTX实验室一直在推进混合现实技术&#xff0c;与美国空军密切合作&#xff0c;通过其EMPACT平台提供可扩展的沉浸式飞机维护虚拟现实培训。 虚拟和混合现实对维修训练的好处&#xff1a; l 实践技能&#xff1a;提供一个非常接近真实场…...