ffplay播放器剖析(8)----逐帧/音量调节/快进快退/倍数分析
文章目录
- 1.逐帧播放
- 2. 音量调节
- 3. seek 快进 快退
- 4.倍速
1.逐帧播放
逐帧播放就是按s键触发的,调用step_to_next_frame触发
static void step_to_next_frame(VideoState *is)
{/* if the stream is paused unpause it, then step */if (is->paused)stream_toggle_pause(is);is->step = 1;
}
step就是启动逐帧播放模式,如果是暂停状态就会改为继续播放状态
看一下那里使用了step这个变量
-
逐帧状态下是不会丢帧的
if (frame_queue_nb_remaining(&is->pictq) > 1) {//有nextvp才会检测是否该丢帧Frame *nextvp = frame_queue_peek_next(&is->pictq);duration = vp_duration(is, vp, nextvp);if(!is->step // 非逐帧模式才检测是否需要丢帧 is->step==1 为逐帧播放&& (framedrop>0 || // cpu解帧过慢(framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) // 非视频同步方式&& time > is->frame_timer + duration // 确实落后了一帧数据) {printf("%s(%d) dif:%lfs, drop frame\n", __FUNCTION__, __LINE__,(is->frame_timer + duration) - time);is->frame_drops_late++; // 统计丢帧情况frame_queue_next(&is->pictq); // 这里实现真正的丢帧//(这里不能直接while丢帧,因为很可能audio clock重新对时了,这样delay值需要重新计算)goto retry; //回到函数开始位置,继续重试}}frame_queue_next(&is->pictq); // 当前vp帧出队列is->force_refresh = 1; /* 说明需要刷新视频帧 */if (is->step && !is->paused)stream_toggle_pause(is); // 逐帧的时候那继续进入暂停状态这个代码逻辑是读取一帧数据,然后调用stream_toggle_pause暂停,如果播放一帧数据后不会继续往下播放,等待s键触发让其继续并且step

2. 音量调节
调节音量就是通过is->audio_volume这个变量进行设置的,因此有一个update_volume函数来修改is->audio_volume的值
static void update_volume(VideoState *is, int sign, double step)
{double volume_level = is->audio_volume ? (20 * log(is->audio_volume / (double)SDL_MIX_MAXVOLUME) / log(10)) : -1000.0;int new_volume = lrint(SDL_MIX_MAXVOLUME * pow(10.0, (volume_level + sign * step) / 20.0));is->audio_volume = av_clip(is->audio_volume == new_volume ? (is->audio_volume + sign) : new_volume, 0, SDL_MIX_MAXVOLUME);
}
这个计算方式不做具体讲解,具体增长曲线类似于慢增长
调节音量使用SDL_MixAudioFormat函数:
memset(stream, 0, len1);
// 3.调整音量
/* 如果处于mute状态则直接使用stream填0数据, 暂停时is->audio_buf = NULL */
if (!is->muted && is->audio_buf)SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index,AUDIO_S16SYS, len1, is->audio_volume);
就是SDL_MixAudioFormat调用audio_volume进行修改音量,一开始是都初始化为0,如果不是静音的话那么就修改音量,否则就是0给SDL输出,因此可以得知静音只需要初始化为0即可!
3. seek 快进 快退
快进 快退就是让媒体文件跳转到一个时间点进行播放
ffplay有两种方案一种是按字节,一种是按时间
if (seek_by_bytes) {pos = -1;if (pos < 0 && cur_stream->video_stream >= 0)pos = frame_queue_last_pos(&cur_stream->pictq);if (pos < 0 && cur_stream->audio_stream >= 0)pos = frame_queue_last_pos(&cur_stream->sampq);if (pos < 0)pos = avio_tell(cur_stream->ic->pb);if (cur_stream->ic->bit_rate)incr *= cur_stream->ic->bit_rate / 8.0;elseincr *= 180000.0;pos += incr;stream_seek(cur_stream, pos, incr, 1);} else {pos = get_master_clock(cur_stream);if (isnan(pos))pos = (double)cur_stream->seek_pos / AV_TIME_BASE;pos += incr; // 现在是秒的单位if (cur_stream->ic->start_time != AV_NOPTS_VALUE && pos < cur_stream->ic->start_time / (double)AV_TIME_BASE)pos = cur_stream->ic->start_time / (double)AV_TIME_BASE;stream_seek(cur_stream, (int64_t)(pos * AV_TIME_BASE), (int64_t)(incr * AV_TIME_BASE), 0);}
快进之前先获取上一帧pts然后赋值给pos,incr是快进时间,如果通过码流算出对应的是位置,然后pos加上incr后通过stream_seek进行seek
不同的方案是由flags来确定的,当我们使用avformat_seek_file时会传入flags,然后avformat_seek_file会自己判断
static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes)
{if (!is->seek_req) {is->seek_pos = pos; // 按时间微秒,按字节 byteis->seek_rel = rel;is->seek_flags &= ~AVSEEK_FLAG_BYTE; // 不按字节的方式去seekif (seek_by_bytes)is->seek_flags |= AVSEEK_FLAG_BYTE; // 强制按字节的方式去seekis->seek_req = 1; // 请求seek, 在read_thread线程seek成功才将其置为0SDL_CondSignal(is->continue_read_thread);}
}
stream_seek最主要的设置了seek_pos和seek_flags,然后通知读线程,
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;// 前进seek seek_rel>0//seek_min = seek_target - is->seek_rel + 2;//seek_max = INT64_MAX;// 后退seek seek_rel<0//seek_min = INT64_MIN;//seek_max = seek_target + |seek_rel| -2;//seek_rel =0 鼠标直接seek//seek_min = INT64_MIN;//seek_max = 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);......
成功之后就会清空队列,然后packet队列中插入一个flush_pkt,frame队列读取到flush_pkt时也会清空队列!
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);}}
4.倍速
后续补充
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
