ffmpeg ffplay.c 源码分析二:数据读取线程
本章主要是分析 数据读取线程read_thread 中的工作。如上图红色框框的部分
- 准备⼯作
- For循环读取数据
- 退出线程处理
一 准备⼯作
1. avformat_alloc_context 创建上下⽂
// 1. 创建上下⽂结构体,这个结构体是最上层的结构体,表示输⼊上下⽂
ic = avformat_alloc_context();
is->ic = ic; // videoState的ic指向分配的ic
2 ic->interrupt_callback
ic 这里是AVFormatContext ,
/* 2.设置中断回调函数,如果出错或者退出,就根据目前程序设置的状态选择继续check或者直接退出 *//* 当open出现阻塞的时候时候,会调用interrupt_callback.callback* 回调函数中返回1则代表ffmpeg结束阻塞可以将操纵权交给用户线程并返回错误码* 回调函数中返回0则代表ffmpeg继续阻塞直到ffmpeg正常工作为止,所以要退出死等则需要返回1*/ic->interrupt_callback.callback = decode_interrupt_cb;ic->interrupt_callback.opaque = is;
1 #0 decode_interrupt_cb (ctx=0x7ffff7e36040) at fftools/ffplay.c:271
5
2 #1 0x00000000007d99b7 in ff_check_interrupt (cb=0x7fffd00014b0)
3 at libavformat/avio.c:667
4 #2 retry_transfer_wrapper (transfer_func=0x7dd950 <file_read>, size
_min=1,
5 size=32768, buf=0x7fffd0001700 "", h=0x7fffd0001480)
6 at libavformat/avio.c:374
7 #3 ffurl_read (h=0x7fffd0001480, buf=0x7fffd0001700 "", size=32768)
8 at libavformat/avio.c:411
9 #4 0x000000000068cd9c in read_packet_wrapper (size=<optimized out>,
10 buf=<optimized out>, s=0x7fffd00011c0) at libavformat/aviobuf.c:
535
11 #5 fill_buffer (s=0x7fffd00011c0) at libavformat/aviobuf.c:584
12 #6 avio_read (s=s@entry=0x7fffd00011c0, buf=0x7fffd0009710 "",
13 size=size@entry=2048) at libavformat/aviobuf.c:677
14 #7 0x00000000006b7780 in av_probe_input_buffer2 (pb=0x7fffd00011c0,
15 fmt=0x7fffd0000948,
16 filename=filename@entry=0x31d50e0 "source.200kbps.768x320.flv",
17 logctx=logctx@entry=0x7fffd0000940, offset=offset@entry=0,
18 max_probe_size=1048576) at libavformat/format.c:262
19 #8 0x00000000007b631d in init_input (options=0x7fffdd9bcb50,
20 filename=0x31d50e0 "source.200kbps.768x320.flv", s=0x7fffd000094
0)
21 at libavformat/utils.c:443
22 #9 avformat_open_input (ps=ps@entry=0x7fffdd9bcbf8,
23 filename=0x31d50e0 "source.200kbps.768x320.flv", fmt=<optimized
out>,
可以看到是在libavformat/avio.c:374⾏有触发到
3.avformat_open_input()打开媒体⽂件
Open an input stream and read the header,打开文件后读取头部。这里有个问题,有些格式其实是没有头部的,因此只调用avformat_open_input是不行的,还需要调用avformat_find_stream_info,avformat_find_stream_info方法是会读取数据的一部分,因此通常情况下 ,这两函数会一起使用。
//特定选项处理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.\n", t->key);ret = AVERROR_OPTION_NOT_FOUND;goto fail;}

scan_all_pmts是mpegts的⼀个选项,表示扫描全部的ts流的"Program Map Table"表。这⾥在没有设定 该选项的时候,强制设为1。最后执⾏avformat_open_input。
使⽤gdb跟踪options的设置,在av_opt_set打断点,,这个有啥用?
(gdb) b av_opt_set
(gdb) r
#0 av_opt_set_dict2 (obj=obj@entry=0x7fffd0000940,
options=options@entry=0x7fffdd9bcb50, search_flags=search_flags@entry=0)
at libavutil/opt.c:1588
#1 0x00000000011c6837 in av_opt_set_dict (obj=obj@entry=0x7fffd0000940,
options=options@entry=0x7fffdd9bcb50) at libavutil/opt.c:1605
#2 0x00000000007b5f8b in avformat_open_input (ps=ps@entry=0x7fffdd9bcbf8,
filename=0x31d23d0 "source.200kbps.768x320.flv", fmt=<optimized out>,
options=0x2e2d450 <format_opts>) at libavformat/utils.c:560
#3 0x00000000004a70ae in read_thread (arg=0x7ffff7e36040)
at fftools/ffplay.c:2780
......
(gdb) l
1583
1584 if (!options)
1585 return 0;
1586
1587 while ((t = av_dict_get(*options, "", t, AV_DICT_IGNORE_SUFFIX))) {
1588 ret = av_opt_set(obj, t->key, t->value, search_flags);
1589 if (ret == AVERROR_OPTION_NOT_FOUND)
1590 ret = av_dict_set(&tmp, t->key, t->value, 0);
1591 if (ret < 0) {
1592 av_log(obj, AV_LOG_ERROR, "Error setting option %s to value %s.\n", t->key, t->value);
(gdb) print **options
$3 = {count = 1, elems = 0x7fffd0001200}
(gdb) print (*options)->elems
$4 = (AVDictionaryEntry *) 0x7fffd0001200
(gdb) print *((*options)->elems)
$5 = {key = 0x7fffd0001130 "scan_all_pmts", value = 0x7fffd0001150 "1"}
(gdb)
参数的设置最终都是设置到对应的解复⽤器,⽐如:
4. avformat_find_stream_info()
* Read packets of a media file to get stream information. This
* is useful for file formats with no headers such as MPEG. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
明白的说:
5 检测是否指定播放起始时间
1 { "ss", HAS_ARG, { .func_arg = opt_seek }, "seek to a given position
in seconds", "pos" },
2 { "t", HAS_ARG, { .func_arg = opt_duration }, "play \"duration\" sec
onds of audio/video", "duration" }
/* if seeking requested, we execute it *//* 如果需要指定起始位置 */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;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);}}
6 查找查找AVStream
- -ast n 指定⾳频流(⽐如我们在看电影时,有些电影可以⽀持普通话和英⽂切换,此时可以⽤该命令 进⾏选择)
- -vst n 指定视频流
- -vst n 指定字幕流
//根据用户指定来查找流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\n", wanted_stream_spec[i], av_get_media_type_string(i));st_index[i] = INT_MAX;}}//利用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);
- 如果⽤户没有指定流,或指定部分流,或指定流不存在,则主要由av_find_best_stream发挥作⽤。
- 如果指定了正确的wanted_stream_nb,⼀般情况都是直接返回该指定流,即⽤户选择的流。
- 如果指定了相关流,且未指定⽬标流的情况,会在相关流的同⼀个节⽬中查找所需类型的流,但⼀般结 果,都是返回该类型第1个流。
7 通过AVCodecParameters和av_guess_sample_aspect_ratio计算出显 示窗⼝的宽、⾼
//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);}}
8 stream_component_open()
/* open a given stream. Return 0 if OK */
/*** @brief stream_component_open* @param is* @param stream_index 流索引* @return Return 0 if OK*/
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;// 设置pkt_timebaseavctx->pkt_timebase = ic->streams[stream_index]->time_base;/* 根据codec_id查找解码器 */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,"No codec could be found with name '%s'\n", forced_codec_name);else av_log(NULL, AV_LOG_WARNING,"No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id));ret = AVERROR(EINVAL);goto fail;}avctx->codec_id = codec->id;if (stream_lowres > codec->max_lowres) {av_log(avctx, AV_LOG_WARNING, "The maximum value for lowres supported by the decoder is %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, "Option %s not found.\n", t->key);ret = AVERROR_OPTION_NOT_FOUND;goto fail;}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 = 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);}
#elsesample_rate = avctx->sample_rate;nb_channels = avctx->channels;channel_layout = avctx->channel_layout;
#endif/* prepare audio output 准备音频输出*/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;/* init averaging filter 初始化averaging滤镜, 非audio master时使用 */is->audio_diff_avg_coef = exp(log(0.01) / AUDIO_DIFF_AVG_NB);is->audio_diff_avg_count = 0;/* 由于我们没有精确的音频数据填充FIFO,故只有在大于该阈值时才进行校正音频同步*/is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;is->audio_stream = stream_index; 获取audio的stream索引is->audio_st = ic->streams[stream_index];// 获取audio的stream指针// 初始化ffplay封装的⾳频解码器decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);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;// 获取video的stream索引is->video_st = ic->streams[stream_index];// 获取video的stream// 初始化ffplay封装的视频解码器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;// 使能请求mp3、aac等⾳频⽂件的封⾯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;}goto out;fail:avcodec_free_context(&avctx);
out:av_dict_free(&opts);return ret;
}
- d->avctx = avctx; 绑定对应的解码器上下⽂
- d->queue = queue; 绑定对应的packet队列
- d->empty_queue_cond = empty_queue_cond; 绑定VideoState的continue_read_thread,当
- 解码线程没有packet可读时唤醒read_thread赶紧读取数据
- d->start_pts = AV_NOPTS_VALUE; 初始化start_pts
- d->pkt_serial = -1; 初始化pkt_serial
- packet_queue_start 启⽤对应的packet 队列
- SDL_CreateThread 创建对应的解码线程
二 For循环读取数据
- a. 是否循环播放
- b. 是否⾃动退出
1. 检测是否退出
///1.检测是否退出if (is->abort_request)break;
2. 检测是否暂停/继续
///2. 检查是否暂停,这⾥的暂停、继续只是对⽹络流有意义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);}
av_read_pause(ic);方法和 av_read_play(ic);方法 的内部实现都是会调用真正的 解码器的方法
int av_read_pause(AVFormatContext *s)
{if (s->iformat->read_pause)return s->iformat->read_pause(s);if (s->pb)return avio_pause(s->pb, 1);return AVERROR(ENOSYS);
}
例如:⽐如rtsp rtsp_read_pause方法 和 rtsp_read_play方法
/* pause the stream */
static int rtsp_read_pause(AVFormatContext *s)
{RTSPState *rt = s->priv_data;RTSPMessageHeader reply1, *reply = &reply1;if (rt->state != RTSP_STATE_STREAMING)return 0;else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL);if (reply->status_code != RTSP_STATUS_OK) {return ff_rtsp_averror(reply->status_code, -1);}}rt->state = RTSP_STATE_PAUSED;return 0;
}
static int rtsp_read_play(AVFormatContext *s)
{RTSPState *rt = s->priv_data;RTSPMessageHeader reply1, *reply = &reply1;int i;char cmd[MAX_URL_SIZE];av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state);rt->nb_byes = 0;if (rt->lower_transport == RTSP_LOWER_TRANSPORT_UDP) {for (i = 0; i < rt->nb_rtsp_streams; i++) {RTSPStream *rtsp_st = rt->rtsp_streams[i];/* Try to initialize the connection state in a* potential NAT router by sending dummy packets.* RTP/RTCP dummy packets are used for RDT, too.*/if (rtsp_st->rtp_handle &&!(rt->server_type == RTSP_SERVER_WMS && i > 1))ff_rtp_send_punch_packets(rtsp_st->rtp_handle);}}if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) {if (rt->transport == RTSP_TRANSPORT_RTP) {for (i = 0; i < rt->nb_rtsp_streams; i++) {RTSPStream *rtsp_st = rt->rtsp_streams[i];RTPDemuxContext *rtpctx = rtsp_st->transport_priv;if (!rtpctx)continue;ff_rtp_reset_packet_queue(rtpctx);rtpctx->last_rtcp_ntp_time = AV_NOPTS_VALUE;rtpctx->first_rtcp_ntp_time = AV_NOPTS_VALUE;rtpctx->base_timestamp = 0;rtpctx->timestamp = 0;rtpctx->unwrapped_timestamp = 0;rtpctx->rtcp_ts_offset = 0;}}if (rt->state == RTSP_STATE_PAUSED) {cmd[0] = 0;} else {snprintf(cmd, sizeof(cmd),"Range: npt=%"PRId64".%03"PRId64"-\r\n",rt->seek_timestamp / AV_TIME_BASE,rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000);}ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL);if (reply->status_code != RTSP_STATUS_OK) {return ff_rtsp_averror(reply->status_code, -1);}if (rt->transport == RTSP_TRANSPORT_RTP &&reply->range_start != AV_NOPTS_VALUE) {for (i = 0; i < rt->nb_rtsp_streams; i++) {RTSPStream *rtsp_st = rt->rtsp_streams[i];RTPDemuxContext *rtpctx = rtsp_st->transport_priv;AVStream *st = NULL;if (!rtpctx || rtsp_st->stream_index < 0)continue;st = s->streams[rtsp_st->stream_index];rtpctx->range_start_offset =av_rescale_q(reply->range_start, AV_TIME_BASE_Q,st->time_base);}}}rt->state = RTSP_STATE_STREAMING;return 0;
}
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\n", 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);// 如果本身是pause状态的则显示⼀帧继续暂停}
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(©, &is->video_st->attached_pic)) < 0)goto fail;packet_queue_put(&is->videoq, ©);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 *//* 缓存队列有足够的包,不需要继续读取数据 */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;}
static int stream_has_enough_packets(AVStream *st, int stream_id, PacketQueue *queue) {return stream_id < 0 || ///没有该流queue->abort_request || /// 请求退出(st->disposition & AV_DISPOSITION_ATTACHED_PIC) || /// 是ATTACHED_PICqueue->nb_packets > MIN_FRAMES ///packet数量大于25&& (!queue->duration || /// 满⾜PacketQueue总时⻓为0av_q2d(st->time_base) * queue->duration > 1.0);///或总时⻓超过1s
}
- 1. 流没有打开(stream_id < 0),没有相应的流返回逻辑true
- 2. 有退出请求(queue->abort_request)
- 3. 配置了AV_DISPOSITION_ATTACHED_PIC
- 4. packet队列内包个数⼤于MIN_FRAMES(>25),并满⾜PacketQueue总时⻓为0或总时⻓超过1s
- 总数据⼤⼩
- 每个packet队列的情况。
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 && (!loop || --loop)) {stream_seek(is, start_time != AV_NOPTS_VALUE ? start_time : 0, 0, 0);} else if (autoexit) {ret = AVERROR_EOF;goto fail;}}
7. 使⽤av_read_frame读取数据包
/* 7.读取媒体数据,得到的是音视频分离后、解码前的数据 */ret = av_read_frame(ic, pkt); // 调用不会释放pkt的数据,都是自己重新申请
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; // 继续循环,保证线程的运行,比如要seek到某个位置播放可以继续响应} else {is->eof = 0;}
9. 检测是否在播放范围内
播放器可以设置:-ss 起始位置,以及 -t 播放时⻓
// 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;// 这里的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);
(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. 到这步才将数据插⼊对应的队列
// 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\n", 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);// C++ share_ptr}
三 退出线程处理
相关文章:

ffmpeg ffplay.c 源码分析二:数据读取线程
本章主要是分析 数据读取线程read_thread 中的工作。如上图红色框框的部分 从ffplay框架分析我们可以看到,ffplay有专⻔的线程read_thread()读取数据, 且在调⽤av_read_frame 读取数据包之前需要做: 1.例如打开⽂件, 2.查找配置解…...

国科大作业考试资料《人工智能原理与算法》2024新编-第十三次作业整理
1、假设我们从决策树生成了一个训练集,然后将决策树学习应用于该训练集。当训练集的大小趋于无穷时,学习算法将最终返回正确的决策树吗?为什么是或不是? 本次有两个参考: 参考一: 当训练集的大小趋于无穷…...

Netdevops入门之Telnetlib语法案例
1、Telnetlib模块: 支持telnet/ssh远程访问的模块很多,常见的有telnetlib、ciscolib、paramiko、netmiko、pexpect,其中telnetlib和ciscolib对应telnet协议,后面3个对应SSH协议。 ①-通过ENSP环境搭建实验环境 ②-基础语法-telnetlib案例1&…...

永辉“爆改”续命
2024年,在线下零售一片哀嚎之下,胖东来似乎活成了国内零售业的密码,同时也变身成为各大零售企业的咨询公司,四处帮助“友商”救火,就连一直名声在外的永辉超市,也成了救火对象。 作为曾经国内生鲜超市的“…...

IEEE双一区Top“饱受诟病”!曾上医院黑名单,国人占比高达82.405%,目测即将拉下神坛?
本周投稿推荐 SCI&EI • 1区计算机类,3.5-4.0(1个月录用) • CCF推荐,1区-Top(3天初审) EI • 各领域沾边均可(2天录用) 知网(CNKI)、谷歌学术 •…...

Hive环境搭建(Mysql数据库)
【实验目的】 1) 了解hive的作用 2) 熟练hive的配置过程(Mysql数据库) 【实验原理】 Hive工具中默认使用的是derby数据库,该数据库使用简单,操作灵活,但是存在一定的局限性,hive支持使用第三方数据库&…...

【ESP32 IDF SPI硬件驱动W25Q64】
目录 SPISPI介绍idf配置初始化配置通信 驱动代码 SPI SPI介绍 详细SPI介绍内容参考我之前写的内容【ESP32 IDF 软件模拟SPI驱动 W25Q64存储与读取数组】 idf配置 初始化配置 spi_bus_initialize() 参数1 :spi几,例如spi2,spi3 参数2:…...

太原高校大学智能制造实验室数字孪生可视化系统平台建设项目验收
随着科技的不断进步,智能制造已经成为推动制造业转型升级的重要力量。太原高校大学智能制造实验室紧跟时代步伐,积极推进数字孪生可视化系统平台的建设,并于近日圆满完成了项目的验收工作。这一里程碑式的成果,不仅标志着实验室在…...

Kafka消息队列
目录 什么是消息队列 高可用性 高扩展性 高可用性 持久化和过期策略 consumer group 分组消费 ZooKeeper 什么是消息队列 普通版消息队列 说白了就是一个队列,生产者生产多少,放在消息队列中存储,而消费者想要多少拿多少,按序列号消费 缓存信息 生产者与消费者解耦…...

@Transactional注解及其事务管理
1. 事务问题概述 事务问题主要来源于数据库,与数据库事务紧密相关。事务的四大特性(ACID): 原子性(Atomicity):事务要么完全执行,要么完全不执行。一致性(Consistency&a…...

ROS2入门到精通—— 3-1 ROS2实战:CasADi —— 优化计算的利器
0 前言 CasADi是一个强大的开源软件库,它提供了一种灵活且高效的方式来构建和解决复杂的非线性模型。通过其直观的API,开发者可以轻松地定义数学表达式并自动求解微分方程、优化问题以及符号计算等任务。 CasADi基于Python编写,但提供了C++和MATLAB接口,使得不同背景的开发…...

日拱一卒 | JVM
文章目录 什么是JVM?JVM的组成JVM的大致工作流程JVM的内存模型 什么是JVM? 我们知道Java面试,只要你的简历上写了了解JVM,那么你就必然会被问到以下问题: 什么是JVM?简单说一下JVM的内存模型?…...

乐尚代驾六订单执行一
加载当前订单 需求 无论是司机端,还是乘客端,遇到页面切换,重新登录小程序等,只要回到首页面,查看当前是否有正在执行订单,如果有跳转到当前订单执行页面 之前这个接口已经开发,为了测试&…...

SciPy 与 MATLAB 数组
SciPy 与 MATLAB 数组 SciPy 是一个开源的 Python 库,广泛用于科学和工程计算。它构建在 NumPy 数组的基础之上,提供了许多高级科学计算功能。MATLAB 是一个高性能的数值计算环境,它也使用数组作为其基础数据结构。在这篇文章中,我们将探讨 SciPy 和 MATLAB 在数组操作上的…...

基于vue-grid-layout插件(vue版本)实现增删改查/拖拽自动排序等功能(已验证、可正常运行)
前端时间有个需求,需要对33(不一定,也可能多行)的卡片布局,进行拖拽,拖拽过程中自动排序,以下代码是基于vue2,可直接运行,报错可评论滴我 部分代码优化来自于GPT4o和Clau…...

DBoW3相关优化脉络
1 DBow3 GitHub - rmsalinas/DBow3: Improved version of DBow2 2 优化后得到fbow GitHub - rmsalinas/fbow: FBOW (Fast Bag of Words) is an extremmely optimized version of the DBow2/DBow3 libraries. 其中fbow是ucoslam的一部分; ucoslam GitHub - la…...

qt 如何制作动态库插件
首先 首先第一点要确定我们的接口是固定的,也就是要确定 #ifndef RTSPPLUGIN_H #define RTSPPLUGIN_H #include "rtspplugin_global.h" typedef void (*func_callback)(uint8_t* data,int len,uint32_t ssrc,uint32_t ts,const char* ipfrom,uint16_t f…...

一种docker start放回Error response from daemon: task xxx错误的解决方式
1. 问题描述 执行systemctl daemon-reload与systemctl restart docker命令后,发现docker中有的应用无法启动,并显示出Exit(255)的错误提示。 重新执行docker start 容器id后发现返回,Error response from daemon: task xxx的错误。 2. 问题…...

规控面试常见问题
一、项目中遇到的困难或者挑战是什么? 二、A*算法原理(伪代码) 输入:代价地图、start 、 goal(Node结构,包含x、y、g、h、id、pid信息) 首先初始化:创建一个优先级队列openlist,它是一个最小堆,根据节点的f值排序 ( priority_queue<Node, std::vector<Node…...

代码随想录算法训练营Day 63| 图论 part03 | 417.太平洋大西洋水流问题、827.最大人工岛、127. 单词接龙
代码随想录算法训练营Day 63| 图论 part03 | 417.太平洋大西洋水流问题、827.最大人工岛、127. 单词接龙 文章目录 代码随想录算法训练营Day 63| 图论 part03 | 417.太平洋大西洋水流问题、827.最大人工岛、127. 单词接龙17.太平洋大西洋水流问题一、DFS二、BFS三、本题总结 82…...

【全网最全】CSDN博客的文字颜色、字体和字号设置
文章目录 一、字体颜色二、字体大小三、字体类型四、字体背景色 在这篇博客中,我们将深入探讨如何在Markdown编辑器中设置文字颜色、大小、字体与背景色。Markdown本身并不直接支持这些功能,但通过结合HTML标签和CSS样式,我们可以实现这些视觉…...

C#实现数据采集系统-Mqtt实现采集数据转发
在数据采集系统中,通过ModbusTcp采集到数据之后,再通过MQTT转发到其他应用 MQTT操作 安装MQTT mqtt介绍和环境安装 使用MQTT 在C#/Net中使用Mqtt MQTT类封装 MQTT配置类 public class MqttConfig{public string Ip {get; set;...

common-intellisense:助力TinyVue 组件书写体验更丝滑
本文由体验技术团队Kagol原创~ 前两天,common-intellisense 开源项目的作者 Simon-He95 在 VueConf 2024 群里发了一个重磅消息: common-intellisense 支持 TinyVue 组件库啦! common-intellisense 插件能够提供超级强大的智能提示功能&…...

图片在线压缩有效方法详解,分享7款最佳图片压缩工具免费(全新)
当您的系统中图片数量不断增加,却无法删除时,那么就需要通过图片压缩来解决您的问题。随着图片文件的增大,高分辨率图片占据了大量存储空间。而此时系统中的存储空间也开始变得不够用,无法跟上高质量图片的增长。因此,…...

electron安装及快速创建
electron安装及快速创建 electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 详细内容见官网:https://www.electronjs.org/zh/docs/latest/。 今天来记录下练习中的安装过程和hello world的创建。 创建项目文件夹,并执行npm 初始化命…...

需要消化的知识点
需要消化 消灭清单 如何自定义一个Interceptor拦截器? 后端开发可以用上的前端技巧 10个堪称神器的 Java 学习网站. 【前端胖头鱼】11 chrome高级调试技巧,学会效率直接提升666% 【前端胖头鱼】10个我经常逛的“小网站” 【前端劝退师lv-6】Chrome D…...

2024年7月25日(Git gitlab以及分支管理 )
分布式版本控制系统 一、Git概述 Git 是一种分布式版本控制系统,用于跟踪和管理代码的变更。它是由Linus Torvalds创建的,最 初被设计用于Linux内核的开发。Git允许开发人员跟踪和管理代码的版本,并且可以在不同的开 发人员之间进行协作。 Github 用的就是Git系统来管理它们的…...

pdf格式过大怎么样变小 pdf文件过大如何缩小上传 超实用的简单方法
面对体积庞大的 PDF 文件,我们常常需要寻找有效的方法来缩减其大小。这不仅能够优化存储空间,还能提升文件的传输和打开速度。PDF文件以其稳定性和跨平台兼容性成为工作和学习中的重要文件格式。然而,当我们需要通过邮件发送或上传大文件时&a…...

前端文件下载word乱码问题
记录一次word下载乱码问题: 用的请求是axios库,然后用Blob去接收二进制文件 思路:现在的解决办法有以下几种,看看是对应哪种,可以尝试解决 1.将响应类型设为blob,这也是最重要的,如果没有解决…...

repo中的default.xml文件project name为什么一样?
文章目录 default.xml文件介绍为什么 name 是一样的,path 不一样?总结 default.xml文件介绍 在 repo 工具的 default.xml 文件中,定义了多个 project 元素,每个元素都代表一个 Git 仓库。 XML 定义了多个不同的 project 元素&…...