逐帧播放就是按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
调节音量就是通过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即可!
快进 快退就是让媒体文件跳转到一个时间点进行播放
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;
else
incr *= 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; // 按时间微秒,按字节 byte
is->seek_rel = rel;
is->seek_flags &= ~AVSEEK_FLAG_BYTE; // 不按字节的方式去seek
if (seek_by_bytes)
is->seek_flags |= AVSEEK_FLAG_BYTE; // 强制按字节的方式去seek
is->seek_req = 1; // 请求seek, 在read_thread线程seek成功才将其置为0
SDL_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);
}
}
后续补充