FFmpeg源码:avio_seek函数分析

=================================================================

AVIOContext结构体和其相关的函数分析:

FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析

FFmpeg源码:read_packet_wrapper、fill_buffer函数分析

FFmpeg源码:avio_read函数分析

FFmpeg源码:avio_seek函数分析

FFmpeg源码:avio_skip函数分析

FFmpeg源码:avio_tell函数分析

FFmpeg源码:ffurl_seek2、ffurl_seek、avio_size函数分析

FFmpeg源码:avio_feof函数分析

=================================================================

一、avio_seek函数的声明

avio_seek函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavformat/avio.h中:

cpp 复制代码
/**
 * fseek() equivalent for AVIOContext.
 * @return new position or AVERROR.
 */
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence);

该函数可以看做是AVIOContext版本的fseek函数,根据传入形参whence的值的不同,avio_seek函数的作用也不同。

情况一:whence的值为SEEK_SET时,执行avio_seek函数后,如果执行成功,AVIOContext的文件位置指针将指向以文件开头为基准,偏移offset(指针偏移量)个字节的位置。

情况二:whence的值为SEEK_CUR时,执行avio_seek函数后,如果执行成功,AVIOContext的文件位置指针将指向以当前位置为基准,偏移offset(指针偏移量)个字节的位置。

情况三:whence的值为SEEK_END时,执行avio_seek函数后,如果执行成功,AVIOContext的文件位置指针将指向以文件结尾为基准,偏移offset(指针偏移量)个字节的位置。

形参s:既是输入型参数也是输出型参数。指向一个AVIOContext(字节流上下文结构体)变量。关于AVIOContext结构体可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》。

形参offset:输入型参数,表示偏移量。正数表示正向偏移,负数表示负向偏移。

形参whence:输入型参数,设定从文件的哪里开始偏移,可能取值为:

cpp 复制代码
/* The possibilities for the third argument to `fseek'.
   These values should not be changed.  */
#define SEEK_SET	0	/* Seek from beginning of file.  */
#define SEEK_CUR	1	/* Seek from current position.  */
#define SEEK_END	2	/* Seek from end of file.  */

SEEK_SET: 文件开头

SEEK_CUR: 当前位置

SEEK_END: 文件结尾

返回值:失败返回一个负数,成功返回偏移值。

二、avio_seek函数的使用例子

avio_seek(s,100,SEEK_SET):把AVIOContext的文件位置指针移动到离文件开头100字节处;

avio_seek(s,100,SEEK_CUR):把AVIOContext的文件位置指针移动到离文件当前位置100字节处;

avio_seek(s,-100,SEEK_END):把AVIOContext的文件位置指针退回到离文件结尾100字节处。

三、avio_seek函数的定义

avio_seek函数定义在源文件libavformat/aviobuf.c中:

cpp 复制代码
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
{
    FFIOContext *const ctx = ffiocontext(s);
    int64_t offset1;
    int64_t pos;
    int force = whence & AVSEEK_FORCE;
    int buffer_size;
    int short_seek;
    whence &= ~AVSEEK_FORCE;

    if(!s)
        return AVERROR(EINVAL);

    if ((whence & AVSEEK_SIZE))
        return s->seek ? s->seek(s->opaque, offset, AVSEEK_SIZE) : AVERROR(ENOSYS);

    buffer_size = s->buf_end - s->buffer;
    // pos is the absolute position that the beginning of s->buffer corresponds to in the file
    pos = s->pos - (s->write_flag ? 0 : buffer_size);

    if (whence != SEEK_CUR && whence != SEEK_SET)
        return AVERROR(EINVAL);

    if (whence == SEEK_CUR) {
        offset1 = pos + (s->buf_ptr - s->buffer);
        if (offset == 0)
            return offset1;
        if (offset > INT64_MAX - offset1)
            return AVERROR(EINVAL);
        offset += offset1;
    }
    if (offset < 0)
        return AVERROR(EINVAL);

    short_seek = ctx->short_seek_threshold;
    if (ctx->short_seek_get) {
        int tmp = ctx->short_seek_get(s->opaque);
        short_seek = FFMAX(tmp, short_seek);
    }

    offset1 = offset - pos; // "offset1" is the relative offset from the beginning of s->buffer
    s->buf_ptr_max = FFMAX(s->buf_ptr_max, s->buf_ptr);
    if ((!s->direct || !s->seek) &&
        offset1 >= 0 && offset1 <= (s->write_flag ? s->buf_ptr_max - s->buffer : buffer_size)) {
        /* can do the seek inside the buffer */
        s->buf_ptr = s->buffer + offset1;
    } else if ((!(s->seekable & AVIO_SEEKABLE_NORMAL) ||
               offset1 <= buffer_size + short_seek) &&
               !s->write_flag && offset1 >= 0 &&
               (!s->direct || !s->seek) &&
              (whence != SEEK_END || force)) {
        while(s->pos < offset && !s->eof_reached)
            fill_buffer(s);
        if (s->eof_reached)
            return AVERROR_EOF;
        s->buf_ptr = s->buf_end - (s->pos - offset);
    } else if(!s->write_flag && offset1 < 0 && -offset1 < buffer_size>>1 && s->seek && offset > 0) {
        int64_t res;

        pos -= FFMIN(buffer_size>>1, pos);
        if ((res = s->seek(s->opaque, pos, SEEK_SET)) < 0)
            return res;
        s->buf_end =
        s->buf_ptr = s->buffer;
        s->pos = pos;
        s->eof_reached = 0;
        fill_buffer(s);
        return avio_seek(s, offset, SEEK_SET | force);
    } else {
        int64_t res;
        if (s->write_flag) {
            flush_buffer(s);
        }
        if (!s->seek)
            return AVERROR(EPIPE);
        if ((res = s->seek(s->opaque, offset, SEEK_SET)) < 0)
            return res;
        ctx->seek_count++;
        if (!s->write_flag)
            s->buf_end = s->buffer;
        s->buf_ptr = s->buf_ptr_max = s->buffer;
        s->pos = offset;
    }
    s->eof_reached = 0;
    return offset;
}

四、avio_seek函数的内部实现原理

下面分情况讨论:

(一)whence的值为SEEK_SET

whence的值为SEEK_SET时,avio_seek函数可以化简为:

cpp 复制代码
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
{
    FFIOContext *const ctx = ffiocontext(s);
    int64_t offset1;
    int64_t pos;
    int buffer_size;
    
    if(!s)
        return AVERROR(EINVAL);

    buffer_size = s->buf_end - s->buffer;
    pos = s->pos - (s->write_flag ? 0 : buffer_size);

    if (offset < 0)
        return AVERROR(EINVAL);

    offset1 = offset - pos;
    s->buf_ptr_max = FFMAX(s->buf_ptr_max, s->buf_ptr);
    if ((!s->direct || !s->seek) && offset1 >= 0 && offset1 <= (s->write_flag ? s->buf_ptr_max - s->buffer : buffer_size)) {
        /* can do the seek inside the buffer */
        s->buf_ptr = s->buffer + offset1;
    }
    s->eof_reached = 0;
    return offset;
}

大部分情况下,pos的值为0:

cpp 复制代码
    pos = s->pos - (s->write_flag ? 0 : buffer_size);

这时offset1 = offset - pos

= offset - 0

= offset:

cpp 复制代码
    offset1 = offset - pos;

所以这时s->buf_ptr = s->buffer + offset1

= s->buffer + offset:

cpp 复制代码
        s->buf_ptr = s->buffer + offset1;

所以大部分情况下,执行语句avio_seek(s->pb, offset, SEEK_SET)后,s->pb->buf_ptr的值会变为s->pb->buffer + offset。从而实现让AVIOContext的文件位置指针(s->pb->buf_ptr)指向以文件开头(s->pb->buffer)为基准,偏移offset(指针偏移量)个字节的位置。即把文件位置指针移动到离文件开头offset字节处。

(二)whence的值为SEEK_CUR

whence的值为SEEK_CUR时,avio_seek函数可以化简为:

cpp 复制代码
int64_t avio_seek(AVIOContext *s, int64_t offset, int whence)
{
    FFIOContext *const ctx = ffiocontext(s);
    int64_t offset1;
    int64_t pos;
    int buffer_size;

    if(!s)
        return AVERROR(EINVAL);

    buffer_size = s->buf_end - s->buffer;
    pos = s->pos - (s->write_flag ? 0 : buffer_size);
    
    if (whence == SEEK_CUR) {
        offset1 = pos + (s->buf_ptr - s->buffer);
        if (offset == 0)
            return offset1;
        if (offset > INT64_MAX - offset1)
            return AVERROR(EINVAL);
        offset += offset1;
    }
    if (offset < 0)
        return AVERROR(EINVAL);

    offset1 = offset - pos;
    s->buf_ptr_max = FFMAX(s->buf_ptr_max, s->buf_ptr);
    if ((!s->direct || !s->seek) &&
        offset1 >= 0 && offset1 <= (s->write_flag ? s->buf_ptr_max - s->buffer : buffer_size)) {
        s->buf_ptr = s->buffer + offset1;
    } 

    s->eof_reached = 0;
    return offset;
}

大部分情况下,pos的值为0:

cpp 复制代码
    pos = s->pos - (s->write_flag ? 0 : buffer_size);

这时offset1 = s->buf_ptr - s->buffer。s->buf_ptr - s->buffer为AVIOContext输入缓冲区中当前读取到的位置距离输入缓冲区的开头的偏移。即文件位置指针当前位置(s->buf_ptr)相对于文件首(s->buffer)的偏移字节数:

cpp 复制代码
        offset1 = pos + (s->buf_ptr - s->buffer);

所以这时offset = offset + offset1

= offset + (s->buf_ptr - s->buffer):

cpp 复制代码
offset += offset1;

这时让offset1 = offset - pos

= offset - 0

= offset

= 最开始的形参offset的值 + (s->buf_ptr - s->buffer) :

cpp 复制代码
offset1 = offset - pos; 

所以这时s->buf_ptr = s->buffer + offset1

= s->buffer + 最开始的形参offset的值 + (s->buf_ptr - s->buffer)

= s->buf_ptr + 最开始的形参offset的值:

cpp 复制代码
s->buf_ptr = s->buffer + offset1;

所以大部分情况下,执行语句avio_seek(s->pb, offset, SEEK_CUR)后,s->pb->buf_ptr的值会变为s->pb->buf_ptr+ offset。从而实现让AVIOContext的文件位置指针(s->pb->buf_ptr)指向以当前位置(s->pb->buf_ptr)为基准,偏移offset(指针偏移量)个字节的位置。即把文件位置指针移动到离文件当前位置offset字节处。

(三)whence的值为SEEK_END

暂略,后续补充。

相关推荐
EasyCVR3 小时前
EHOME视频平台EasyCVR视频融合平台使用OBS进行RTMP推流,WebRTC播放出现抖动、卡顿如何解决?
人工智能·算法·ffmpeg·音视频·webrtc·监控视频接入
简鹿办公4 小时前
使用 FFmpeg 进行音视频转换的相关命令行参数解释
ffmpeg·简鹿视频格式转换器·ffmpeg视频转换
EasyCVR7 小时前
萤石设备视频接入平台EasyCVR多品牌摄像机视频平台海康ehome平台(ISUP)接入EasyCVR不在线如何排查?
运维·服务器·网络·人工智能·ffmpeg·音视频
runing_an_min7 小时前
ffmpeg 视频滤镜:屏蔽边框杂色- fillborders
ffmpeg·音视频·fillborders
岁月小龙18 小时前
如何让ffmpeg运行时从当前目录加载库,而不是从/lib64
ffmpeg·origin·ffprobe·rpath
行者记2 天前
ffmpeg命令——从wireshark包中的rtp包中分离h264
测试工具·ffmpeg·wireshark
EasyCVR2 天前
国标GB28181视频平台EasyCVR私有化视频平台工地防盗视频监控系统方案
运维·科技·ffmpeg·音视频·1024程序员节·监控视频接入
hypoqqq2 天前
使用ffmpeg播放rtsp视频流
ffmpeg
cuijiecheng20182 天前
音视频入门基础:FLV专题(24)——FFmpeg源码中,获取FLV文件视频信息的实现
ffmpeg·音视频
QMCY_jason2 天前
黑豹X2 armbian 编译rkmpp ffmpeg 实现CPU视频转码
ffmpeg