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

做网站一般多钱大连甘井子区房价

做网站一般多钱,大连甘井子区房价,开封专业做网站公司,免费建网站无广告本章主要是分析 数据读取线程read_thread 中的工作。如上图红色框框的部分 从ffplay框架分析我们可以看到#xff0c;ffplay有专⻔的线程read_thread()读取数据#xff0c; 且在调⽤av_read_frame 读取数据包之前需要做#xff1a; 1.例如打开⽂件#xff0c; 2.查找配置解… 本章主要是分析 数据读取线程read_thread 中的工作。如上图红色框框的部分 从ffplay框架分析我们可以看到ffplay有专⻔的线程read_thread()读取数据 且在调⽤av_read_frame 读取数据包之前需要做 1.例如打开⽂件 2.查找配置解码器 3.初始化⾳视频输出等准备阶段 主要包括三⼤步骤 准备⼯作 For循环读取数据 退出线程处理 一 准备⼯作 准备⼯作主要包括以下步骤 1. avformat_alloc_context 创建上下⽂ 2. ic - interrupt_callback . callback decode_interrupt_cb; 3. avformat_open_input打开媒体⽂件 4. avformat_find_stream_info 读取媒体⽂件的包获取更多的stream信息 5. 检测是否指定播放起始时间如果指定时间则seek到指定位置 avformat_seek_file 6. 查找 查找AVStream讲对应的index值记录到 st_index [ AVMEDIA_TYPE_NB ] a. 根据⽤户指定来查找流 avformat_match_stream_specifier b. 使⽤av_find_best_stream查找流 7. 通过 AVCodecParameters和 av_guess_sample_aspect_ratio 计算出显示窗⼝的宽、⾼ 8. stream_component_open打开⾳频、视频、字幕解码器并创建相应的解码线程以及进⾏对应输出参数的初始化。 1. avformat_alloc_context 创建上下⽂ 调⽤ avformat_alloc_context创建解复⽤器上下⽂ // 1. 创建上下⽂结构体这个结构体是最上层的结构体表示输⼊上下⽂ ic avformat_alloc_context(); 最终该ic 赋值给VideoState的ic变量 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; 那么这个  interrupt_callback 是个啥 interrupt_callback⽤于ffmpeg内部在执⾏耗时操作时检查调⽤者是否有退出请求避免⽤户退出请求没 有及时响应。 在什么时候触发这个 callback 呢这个知道什么时候触发就可以了 avformat_open_input的触发 avformat_find_stream_info的触发 av_read_frame的触发 在QT中debug 是看不到什么时候触发的测试方法如下 怎么去测试在哪⾥触发 在ubuntu使⽤gdb进⾏调试我们之前讲的在ubuntu下编译ffmpeg在 lqfubuntu:~/ffmpeg_sources/ffmpeg-4.2.1⽬录下有ffplay_g我们可以通过 gdb ./ffplay_g来播 放视频然后在decode_interrupt_cb打断点。 举例avformat_open_input的触发 1 #0 decode_interrupt_cb (ctx0x7ffff7e36040) at fftools/ffplay.c:271 5 2 #1 0x00000000007d99b7 in ff_check_interrupt (cb0x7fffd00014b0) 3 at libavformat/avio.c:667 4 #2 retry_transfer_wrapper (transfer_func0x7dd950 file_read, size _min1, 5 size32768, buf0x7fffd0001700 , h0x7fffd0001480) 6 at libavformat/avio.c:374 7 #3 ffurl_read (h0x7fffd0001480, buf0x7fffd0001700 , size32768) 8 at libavformat/avio.c:411 9 #4 0x000000000068cd9c in read_packet_wrapper (sizeoptimized out, 10 bufoptimized out, s0x7fffd00011c0) at libavformat/aviobuf.c: 535 11 #5 fill_buffer (s0x7fffd00011c0) at libavformat/aviobuf.c:584 12 #6 avio_read (ssentry0x7fffd00011c0, buf0x7fffd0009710 , 13 sizesizeentry2048) at libavformat/aviobuf.c:677 14 #7 0x00000000006b7780 in av_probe_input_buffer2 (pb0x7fffd00011c0, 15 fmt0x7fffd0000948, 16 filenamefilenameentry0x31d50e0 source.200kbps.768x320.flv, 17 logctxlogctxentry0x7fffd0000940, offsetoffsetentry0, 18 max_probe_size1048576) at libavformat/format.c:262 19 #8 0x00000000007b631d in init_input (options0x7fffdd9bcb50, 20 filename0x31d50e0 source.200kbps.768x320.flv, s0x7fffd000094 0) 21 at libavformat/utils.c:443 22 #9 avformat_open_input (pspsentry0x7fffdd9bcbf8, 23 filename0x31d50e0 source.200kbps.768x320.flv, fmtoptimized out, 可以看到是在libavformat/avio.c:374⾏有触发到 3.avformat_open_input()打开媒体⽂件 Open an input stream and read the header打开文件后读取头部。这里有个问题有些格式其实是没有头部的因此只调用avformat_open_input是不行的还需要调用avformat_find_stream_infoavformat_find_stream_info方法是会读取数据的一部分因此通常情况下 这两函数会一起使用。 avformat_open_input⽤于打开输⼊⽂件对于RTMP/RTSP/HTTP⽹络流也是⼀样在ffmpeg内部都 抽象为URLProtocol这⾥描述为⽂件是为了⽅便与后续提到的AVStream的流作区分读取视频⽂件 的基本信息。 需要提到的两个参数是fmt和options。通过fmt可以强制指定视频⽂件的封装options可以传递额外参数 给封装(AVInputFormat)。 主要代码 //特定选项处理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。 参数的设置最终都是设置到对应的解复⽤器⽐如 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 (objobjentry0x7fffd0000940, optionsoptionsentry0x7fffdd9bcb50, search_flagssearch_flagsentry0) at libavutil/opt.c:1588 #1 0x00000000011c6837 in av_opt_set_dict (objobjentry0x7fffd0000940, optionsoptionsentry0x7fffdd9bcb50) at libavutil/opt.c:1605 #2 0x00000000007b5f8b in avformat_open_input (pspsentry0x7fffdd9bcbf8, filename0x31d23d0 source.200kbps.768x320.flv, fmtoptimized out, options0x2e2d450 format_opts) at libavformat/utils.c:560 #3 0x00000000004a70ae in read_thread (arg0x7ffff7e36040) 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() 在打开了⽂件后就可以从AVFormatContext中读取流信息了。⼀般调用avformat_find_stream_info获 取完整的流信息。为什么在调⽤了avformat_open_input后仍然需要调⽤avformat_find_stream_info 才能获取正确的流信息呢看下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. 明白的说 该函数是通过读取媒体⽂件的部分数据来分析流信息。在⼀些缺少头信息的封装下特别有⽤⽐如说 MPEG⾥应该说ts更准确(FLV⽂件也是需要读取packet 分析流信息)。⽽被读取⽤以分析流信息的数 据可能被缓存供av_read_frame时使⽤在播放时并不会跳过这部分packet的读取。 5 检测是否指定播放起始时间 如果指定时间则seek到指定位置avformat_seek_file。 可以通过 ffplay -ss 设置起始时间时间格式hh:mm:ss ⽐如 ffplay -ss 00:00:30 test.flv 则是从30秒的起始位置开始播放。 具体调⽤流程可以在 opt_seek 函数打断点进⾏测试 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 ⼀个媒体⽂件对应有0~n个⾳频流、0~n个视频流、0~n个字幕流⽐如这⾥我们⽤了2_audio.mp4是有 2个⾳频流1个视频流 具体现在那个流进⾏播放我们有两种策略 1. 在播放起始指定对应的流 2. 使⽤缺省的流进⾏播放 1 在播放起始指定对应的流 ffplay是通过通过命令可以指定 { ast , OPT_STRING | HAS_ARG | OPT_EXPERT , { wanted_stream_spec[ AVMEDIA_TYPE_AUDIO ] }, select desired audio stream , stream_specifier }, { vst , OPT_STRING | HAS_ARG | OPT_EXPERT , { wanted_stream_spec[ AVMEDIA_TYPE_VIDEO ] }, select desired video stream , stream_specifier }, { sst , OPT_STRING | HAS_ARG | OPT_EXPERT , { wanted_stream_spec[ AVMEDIA_TYPE_SUBTITLE ] }, select desired subtitle stream , stream_specifier }, 可以通过 -ast n 指定⾳频流⽐如我们在看电影时有些电影可以⽀持普通话和英⽂切换此时可以⽤该命令 进⾏选择 -vst n 指定视频流 -vst n 指定字幕流 讲对应的index值记录到st_index[AVMEDIA_TYPE_NB] 2 使⽤缺省的流进⾏播放 如果我们没有指定则ffplay主要是通过 av_find_best_stream 来选择其原型为 //根据用户指定来查找流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计算出显 示窗⼝的宽、⾼ 具体流程如上所示这⾥实质只是设置了 default_width、default_height 变量的⼤⼩没有真正改变窗 ⼝的⼤⼩。真正调整窗⼝⼤⼩是在视频显示调⽤ video_open()函数进⾏设置。 //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() 经过以上步骤⽂件打开成功且获取了流的基本信息并选择⾳频流、视频流、字幕流。接下来就可以 所选流对应的解码器了。 ⾳频、视频、字幕等流都要调⽤ stream_component_open他们直接有共同的流程也有差异化的流 程差异化流程使⽤switch进⾏区分。具体原型 int stream_component_open ( VideoState * is , int stream_index ) stream_index 先是通过 avcodec_alloc_context3 分配了解码器上下⽂ AVCodecContex 然后通过 avcodec_parameters_to_context 把所选流的解码参数赋给 avctx 最后设了 time_base . 补充 avcodec_parameters_to_context 解码时⽤ avcodec_parameters_from_context则⽤于编 码 /* 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; } 即根据具体的流类型作特定的初始化。但不论哪种流基本步骤都包括了ffplay封装的解码器的初始化和 启动解码器线程 decoder_init 初始化解码器 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 decoder_start启动解码器 packet_queue_start 启⽤对应的packet 队列 SDL_CreateThread 创建对应的解码线程 以上是准备的⼯作我们再来看for循环。 二 For循环读取数据 主要包括以下步骤 1. 检测是否退出 2. 检测是否暂停/继续 3. 检测是否需要seek 4. 检测video是否为attached_pic 5. 检测队列是否已经有⾜够数据 6. 检测码流是否已经播放结束 a. 是否循环播放 b. 是否⾃动退出 7. 使⽤av_read_frame读取数据包 8. 检测数据是否读取完毕 9. 检测是否在播放范围内 10. 到这步才将数据插⼊对应的队列 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.%03PRId64-\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 主要的seek操作通过avformat_seek_file完成该函数的具体使⽤在播放控制seek时做详解。根据 avformat_seek_file的返回值如果seek成功需要 1. 清除PacketQueue的缓存并放⼊⼀个flush_pkt。放⼊的flush_pkt可以让PacketQueue的serial增1以区分seek前后的数据PacketQueue函数的分析 0 该flush_pkt也会触发解码器重新刷新解码 器缓存avcodec_flush_buffers()以避免解码时使⽤了原来的buffer作为参考⽽出现⻢赛克。 2. 同步外部时钟。在后续⾳视频同步的课程中再具体分析。 这⾥还要注意如果播放器本身是pause的状态则 if (is-paused) step_to_next_frame(is); // 如果本身是pause状态的则显示⼀帧继续暂停 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 AV_DISPOSITION_ATTACHED_PIC 是⼀个标志。如果⼀个流中含有这个标志的话那么就是说这个流 是 *.mp3等 ⽂件中的⼀个 Video Stream 。并且该流只有⼀个 AVPacket 也就是 attached_pic 。这个 AVPacket 中所存储的内容就是这个 *.mp3等 ⽂件的封⾯图⽚。 因此也可以很好的解释了⽂章开头提到的为什么 st-disposition AV_DISPOSITION_ATTACHED_PIC 这个操作可以决定是否可以继续向缓冲区中添加 AVPacket 。 if (is-queue_attachments_req) {// attached_pic 附带的图片。比如说一些MP3AAC音频文件附带的专辑封面所以需要注意的是音频文件不一定只存在音频流本身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. 检测队列是否已经有⾜够数据 ⾳频、视频、字幕队列都不是⽆限⼤的如果不加以限制⼀直往队列放⼊packet那将导致队列占⽤⼤量 的内存空间影响系统的性能所以必须对队列的缓存⼤⼩进⾏控制。 PacketQueue默认情况下会有⼤⼩限制达到这个⼤⼩后就需要等待10ms以让消费者——解码线程 能有时间消耗。 /* if the queue are full, no need to read more *//* 缓存队列有足够的包不需要继续读取数据 */if (infinite_buffer1 (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;} 缓冲区满有两种可能 1. audioqvideoqsubtitleq三个PacketQueue的总字节数达到了 MAX_QUEUE_SIZE 15M为什么 是15M这⾥只是⼀个经验计算值⽐如4K视频的码率以50Mbps计算则15MB可以缓存2.4秒从 这么计算实际上如果我们真的是播放4K⽚源15MB是偏⼩的数值有些⽚源⽐较坑 同⼀个⽂件位置 附近的pts差值超过5秒此时如果视频要缓存5秒才能做同步那15MB的缓存⼤⼩就不够了 2. ⾳频、视频、字幕流都已有够⽤的包stream_has_enough_packets 注意3者要同时成⽴ 第⼀种好理解看下第⼆种中的stream_has_enough_packets 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_FRAMES25并满⾜PacketQueue总时⻓为0或总时⻓超过1s 思路 总数据⼤⼩ 每个packet队列的情况。 6. 检测码流是否已经播放结 ⾮暂停状态才进⼀步检测码流是否已经播放完毕注意 数据播放完毕 和码流 数据读取完毕 是两个概 念。 PacketQueue和FrameQueue都消耗完毕才是真正的播放完毕 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;}} 这⾥判断播放已完成的条件需要同时满⾜满⾜ 1. 不在暂停状态 2. ⾳频未打开或者打开了但是解码已解完所有packet⾃定义的解码器decoderserial等于 PacketQueue的serial并且FrameQueue中没有数据帧 PacketQueue.serial - packet.serail - decoder.pkt_serial decoder.finished decoder.pkt_serial is-auddec.finished is-audioq.serial 最新的播放序列的packet都解码完毕 frame_queue_nb_remaining(is-sampq) 0 对应解码后的数据也播放完毕 3. 视频未打开或者打开了但是解码已解完所有packet⾃定义的解码器decoderserial等于 PacketQueue的serial并且FrameQueue中没有数据帧。 在确认⽬前码流已播放结束的情况下 ⽤户有两个变量可以控制播放器⾏为 1. loop: 控制播放次数当前这次也算在内也就是最⼩就是1次了0表示⽆限次 2. autoexit⾃动退出也就是播放完成后⾃动退出。 loop条件简化的⾮常不友好其意思是如果loop1那么已经播了1次了⽆需再seek重新播放如果 loop不是10随意⽆限次循环减1后还⼤于0--loop也允许循环 a. 是否循环播放 如果循环播放即是将⽂件seek到起始位置 stream_seek(is, start_time ! AV_NOPTS_VALUE ? start_time : 0, 0, 0); 这⾥讲的的起始位置不⼀定是从头开始具体也要看⽤户是否指定了起始播放位 置 b. 是否⾃动退出 如果播放完毕⾃动退出 7. 使⽤av_read_frame读取数据包 读取数据包很简单但要注意传⼊的packetav_read_frame不会释放其数据⽽是每次都重新申请数据。 /* 7.读取媒体数据得到的是音视频分离后、解码前的数据 */ret av_read_frame(ic, pkt); // 调用不会释放pkt的数据都是自己重新申请 8. 检测数据是否读取完毕 数据读取完毕后放对应⾳频、视频、字幕队列插⼊“空包”以通知解码器冲刷buffer将缓存的所有数 据都解出来frame并去出来。 然后继续在for{}循环直到收到退出命令或者loop播放或者seek等操作 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); 从流获取的参数 stream_start_time是从当前流AVStream-start_time获取到的时间如果没有定义具体的值则默 认为AV_NOPTS_VALUE即该值是⽆效的那stream_start_time有意义的就是0值 pkt_ts当前packet的时间戳pts有效就⽤pts的pts⽆效就⽤dts的 ffplay播放的参数 duration 使⽤-t value指定的播放时⻓默认值 AV_NOPTS_VALUE即该值⽆效不⽤参考 start_time使⽤“-ss value”指定播放的起始位置默认 AV_NOPTS_VALUE即该值⽆效不⽤参考 pkt_in_play_range的值为0或1 当没有指定duration播放时⻓时很显然duration AV_NOPTS_VALUE的逻辑值为1所以 pkt_in_play_range为1 当duration被指定-t 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 - start_time 是否 duration这⾥分为 stream_start_time是否有效有效就⽤实际值⽆效就是从0开始 start_time 是否有效有效就⽤实际值⽆效就是从0开始 即是pkt_ts - stream_start_time - start_time duration 为了简单这⾥没有考虑时间单位 10. 到这步才将数据插⼊对应的队列 1 // 10 将⾳视频数据分别送⼊相应的 queue 中 这⾥的代码就很直⽩了将packet放⼊到对应的PacketQueue // 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} 三 退出线程处理 主要包括以下步骤 1. 如果解复⽤器有打开则关闭avformat_close_input 2. 调⽤SDL_PushEvent发送退出事件 FF_QUIT_EVENT a. 发送的 FF_QUIT_EVENT退出播放事件由 event_loop()函数相应收到 FF_QUIT_EVENT后调⽤ do_exit()做退出操作。 3. 消耗互斥量 wait_mutex
http://www.dnsts.com.cn/news/209295.html

相关文章:

  • 比较个性的网站网站建设佰金手指科杰二七
  • 通辽网站建设0475seo太原编程培训机构
  • 建站之星网站模版商城备案域名批量查询
  • 南京网站设计网站留言板块怎么做
  • ps可以做网站吗怎么查看小程序的开发公司
  • 苏州企业网站制作多少钱wordpress建站 网盘视频教程
  • 腾讯云建站流程网站建设怎么说服客户
  • 网站建设合同要上印花税吗wordpress 动态图形
  • 网站建设一年多少恰网页设计创意书
  • 浙江苏省城乡建设厅网站网站开发框架排行
  • 无锡网络公司无锡网站推广网易免费企业邮箱登录入口
  • 网站后台管理系统制作网站做的好的公司有
  • 邦利博客网站怎么做的手机版网站打开速度慢
  • 怎么建立一个公司的网站吗wordpress如何调用文章页到首页
  • 综合返利商城网站建设长春网络公司十大排名
  • 淮安网站建设 淮安网站制作深圳广告网站设计制作
  • 国外做家谱的网站网络营销课程实训报告
  • 珠海网站公司哪家好opencart做网站视频
  • 东莞大岭山建网站公司免费自助建站哪家好
  • 四川建设学网官方网站登录管理系统网站模板
  • 蜘蛛云建网站怎样wordpress help
  • 广州网站建设 八爪鱼河北城乡建设厅网站
  • 优秀网站作品下载东莞三合一网站制作
  • 营销管理网站制作抖音代运营策划案
  • 建设项目自主验收公示网站郑州网站推广价格
  • 那个网站专做地质基础信息欢迎访问建设银行网站
  • 网站建设电浙江建设招生网站
  • 智能化网站建设包头建设安全协会网站
  • 参加网站建设项目人员保障体系微信微网站是什么案例
  • dz论坛怎么做视频网站吗上海广告制作公司