ChinaFFmpeg

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 14162|回复: 0

[Linux] 干货——ffmpeg IO抽象层次讲解

[复制链接]
发表于 2016-7-27 10:16:13 | 显示全部楼层 |阅读模式
唐僧说:人人为我,我为人人,世界更美好。只有懂了IO抽象,才能自由的seek
1 具体IO.
    每种格式都对一种具体的IO操作方式,这些操作集通过REGISTER_PROTOCOL(),被注册到系统中
    在ffmpeg中针对不同媒介的数据操作方法被抽象为url_xxx,并记录在结构体 URLProtocol 中。
以文件操作为例:
/libavformat/file.c
URLProtocol ff_file_protocol = {
   .name               = "file",
   .url_open            =file_open,
   .url_read            =file_read,
   .url_write           =file_write,
   .url_seek            = file_seek,
   .url_close           =file_close,
    .url_get_file_handle = file_get_handle,
   .url_check           =file_check,
};
再比如网络协议rtsp操作:
/libavformat/rtpproto.c
URLProtocol ff_rtp_protocol = {
   .name               = "rtp",
    .url_open           = rtp_open,
   .url_read            =rtp_read,
   .url_write           =rtp_write,
   .url_close           =rtp_close,
    .url_get_file_handle = rtp_get_file_handle,
};
2 抽象IO,AVIOContext结构体
2.1 功能
  • 对具体IO进行抽象,统一ffmpeg读取文件流的方式。直白点说,ffmpeg都是调用同一种接口,而不用关心文件的具体读取细节,avio_xxxx。文件的具体读取细节由具体IO实现。
  • 允许用户自定义其他具体IO     
2.2 AVIOContext结构体源码分析
2.2.1 unsigned char * buffer:缓存开始位置,缓存中存储的数据是整个大文件数据的一部分。 *即avio_alloc_contexat函数的参数,用户不能操作。用户通过自定义的read_buffer等函数间接的操作该缓冲区。
在解码的情况下,buffer用于存储ffmpeg读入的数据。例如打开一个视频文件的时候,先把数据从硬盘读入buffer,然后在送给解码器用于解码。
2.2.2 int buffer_size:缓存大小(默认32768)
2.2.3 unsigned char * buf_ptr:buffer中下一个待读数据的位置
2.2.4 unsigned char * buf_end:buffer中视频数据结束的位置。读函数返回的数据小于请求的数据大小是,buf_end可能小于buffer+buffer_size。
2.2.5 void * opaque:URLContext结构体
A private pointer, passed to the read/write/seek/ functions
2.2.6 pos; position in the file of the current buffer
2.2.7 读一个包,将用户自定义的buffer中的数据写到ffmpeg内部的buf中,供ffmpeg进行解复用等处理
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
2.2.8 写一个包,将经过ffmpeg处理后写入ffmpeg内部的buf中的数据写到用户自定义的buffer中,供用户处理这些数据
int (*write_packet)(void *opaque, uint8_t * buf, int buf_size);
2.2.9 对于网络流媒体,暂停(pause)或者恢复(resume)
int (*read_pause)(void * opaque, int pause):
2.2.10 seek到某个时间戳
int64_t (*read_seek)(void * opaque, int stream_index, int64_t timestamp,int flags)
2.2.11 当前的流是否可以seek
int seekable
2.2.12 seek
int64_t (*seek)(void * opaque, int64_t offset, int whence);
2.3 AVIOContext结构体的操作函数——avio_xxx对具体IO的操作进行抽象
2.3.1 简介
为了保证IO 操作的一致性,ffmpeg muxer/demuxer 调用函数一组avio_xxx 进行io操作,包括:avio_rxxx() avio_wxxx() avio_open() avio_close()
它们实际上是对 url_open url_read url_write url_cloge
… 的更高一层的抽象,使用avio_xxx 将隔离具体媒介的差异。
2.3.2 avio_seek(AVIOContext * s, int64_t offset, intwhence)
  • 目的
将AVIOContext的缓存s->buffer中的下一个未读读数据指针buf_ptr指向完整媒体文件中想要偏移的位置。
  • 输入
如果whence=SEEK_SET,那么将buf_ptr所在数据变为完整媒体文件中的offset处 如果whence=SEEK_CUR,假设seek前buf_prt所在数据在完整媒体文件中的偏移量为offset1。那么seek后,将buf_ptr所在数据变为完整整媒体文件偏移offset1+offset处。
  • 返回值
要seek到的完整媒体文件中的偏移量
  • 源码思路
如果seek后的数据在buffer中那么,直接移动buf_prt指针就行了。 如果seek后的数据不在buffer中那么,将buf_ptr和buf_end置为buffer,然后调用用户自定义的seek函数(用户自定义的 seek需要通过移动用户自定义的数据指针实现,下次ffmpeg调用自定义read函数时,读到的第一个数据是这次要seek的数据位置)。这样 ffmpeg下次调用读函数,读到数据后,buf_ptr执行的就是这次要seek的位置。
2.3.3 avio_read
3 自定义IO,使用方式
3.1 第一步:通过调用函数avio_alloc_context,初始化AVIOContext结构体
AVIOContext *avio_alloc_context(unsignedchar *buffer,int buffer_size,
                                int write_flag,  //是否可以写缓存(1=true,0=false) ,一般为读,设置为0
                                void*opaque,   //传输用户自定义数据和函数用的。可以将该数据传到自定义的函数中。
                                int(*read_packet)(void *opaque, uint8_t *buf, int buf_size),
                                int(*write_packet)(void *opaque, uint8_t *buf, int buf_size),
                                int64_t(*seek)(void *opaque, int64_t offset, int whence))
  {
     AVIOContext * s = av_mallocz(sizeof(AVIOContext));
     if (!s)
         return NULL;
      ffio_init_context(s, buffer, buffer_size,write_flag, opaque,
                    read_packet, write_packet,seek);
     return s;
  }
int ffio_init_context(AVIOContext *s,
unsigned char *buffer,
int buffer_size,
int write_flag,
void *opaque,
int (*read_packet)(void *opaque, uint8_t*buf, int buf_size),
int (*write_packet)(void *opaque, uint8_t*buf, int buf_size),
int64_t (*seek)(void *opaque, int64_toffset, int whence))
{
s->buffer      = buffer;
s->orig_buffer_size =
s->buffer_size = buffer_size;
s->buf_ptr     = buffer;
s->opaque      = opaque;
s->direct      = 0;
//write_flag设置为1Buffer可写;否则Buffer只可读。
url_resetbuf(s, write_flag ?AVIO_FLAG_WRITE : AVIO_FLAG_READ);
static int url_resetbuf(AVIOContext * s,int flags)
{
   av_assert1(flags == AVIO_FLAG_WRITE || flags == AVIO_FLAG_READ);
   if (flags & AVIO_FLAG_WRITE) {
       s->buf_end = s->buffer + s->buffer_size;
       s->write_flag = 1;
    }else {
       s->buf_end = s->buffer;
       s->write_flag = 0;
    }
   return 0;
}
s->write_packet    = write_packet;
s->read_packet     = read_packet;
s->seek            = seek;
s->pos             = 0;
s->must_flush      = 0;
s->eof_reached     = 0;
s->error           = 0;
s->seekable        = seek ? AVIO_SEEKABLE_NORMAL : 0;
s->max_packet_size = 0;
s->update_checksum = NULL;
if (!read_packet && !write_flag) {
s->pos     = buffer_size;
s->buf_end = s->buffer + buffer_size;
}
s->read_pause = NULL;
s->read_seek  = NULL;
return 0;
}
3.2 第二步:将自定义IO存到AVFormatContext
// Allocate the AVFormatContext:AVFormatContext* pCtx = avformat_alloc_context();
// Set the IOContext: pCtx->pb = pIOCtx;
3.3 注意seek函数的注册起效果是有条件的
第一步中,如果只注册了read函数,没有注册seek函数。比如avio_alloc_context(xx,read_buffer,xx)。那么默 seek函数是不起作用的,pb->seekable变量为0,底层在read不到数据的时候以为应用层没有注册seek函数,而不会去调用应用层的seek函数。后面如果又要让seek函数起作用(pb->seek=seek_buffer),那么需要修改pb->seekable=1第一步中,如果注册了readseek函数,那么pb->seekable变量为1,默认seek函数是起作用的。
3.4 第三步avformat_open_input()函数内部完成抽象IO到具体IO的映射

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|Archiver|ChinaFFmpeg

GMT+8, 2024-4-26 08:06 , Processed in 0.046569 second(s), 14 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表