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这个变量

  1. 逐帧状态下是不会丢帧的

                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;
                    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);
                }
            }

4.倍速

后续补充

Logo

音视频技术社区,一个全球开发者共同探讨、分享、学习音视频技术的平台,加入我们,与全球开发者一起创造更加优秀的音视频产品!

更多推荐