唐僧说:人人为我,我为人人,世界更美好。只有懂了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设置为1则Buffer可写;否则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;第一步中,如果注册了read和seek函数,那么pb->seekable变量为1,默认seek函数是起作用的。 3.4 第三步avformat_open_input()函数内部完成抽象IO到具体IO的映射
|