FFMPEG小记

来源:互联网 发布:php if 多条件 编辑:程序博客网 时间:2024/06/05 21:14

先吐一下槽,Android下的多媒体框架真不好搞,感觉还是微软的DirectShow框架更容易扩展。如果有人知道其它更好的完成下面任务的方法,请告知我。

 

任务是在现有的音视频格式上做一层包装/加密,打开这种新格式文件时文件流要先经过一层自定义的解包处理,再把解包后的文件流交由解码器处理。

 

任务看似简单,在DirectShow框架中只需要做个源过滤器,随后即可调用现有的解码器等设施成功解码。但Android上就挣扎了。现有的App框架完全满足不了需求(或许是我没找到),使用opencore又似乎太庞大而且工作量不小,最终选择了FFMPEG。

 

通过FFMPEG完成上面的任务主要途径有两种:一是修改打开文件读文件流识别解码器部分和解码器读文件流部分;二是重新写一个解码器(这似乎和在opencore中实现一个解码器相当)。

 

分析一下打开文件读文件流识别解码器的部分吧,入口函数是av_open_input_file,其最重要的作用就是填充好AVFormatContext结构。

 

AVFormatContext结构是解析文件格式、找到对应的解码器的重要的结构之一。其定义略长,在此简述。

struct AVInputFormat *iformat;

struct AVOutputFormat *oformat;

ByteIOContext *pb;

iformat和oformat两者只有一个会生效,如果是播放iformat生效,录制则oformat生效。当然还有转码的过程中,输入iformat生效,输出oformat生效。

pb是对文件流操作的一个抽象,接下来讲解。

 

FFMPEG打开文件后会读一段文件流,根据文件流中的内容来找到一个对应AVInputFormat,即一种文件格式的抽象。比如mp4,它的AVInputFormat定义在/libffmpeg/libavformat/mov.c中:

AVInputFormat mov_demuxer = {    "mov,mp4,m4a,3gp,3g2,mj2",    NULL_IF_CONFIG_SMALL("QuickTime/MPEG-4/Motion JPEG 2000 format"),    sizeof(MOVContext),    mov_probe,    mov_read_header,    mov_read_packet,    mov_read_close,    mov_read_seek,};


AVInputFormat定义在/libffmpeg/libavformat/avformat.h中,其定义略长,在此简述。

const char *name;    // 格式别名

const char *long_name;    // 格式的全名,方便人阅读

int priv_data_size;    // 额外数据长度

int (*read_probe)(AVProbeData *);    // 读取适配数据的函数指针

...    // 其它函数指针

 

FFMPEG努力用C来实现面向对象,其中结构体中含函数指针的写法非常常见。所有格式的AVInputFormat(还有AVOutput协议)都会在/libffmpeg/libavformat/Allformats.c的av_register_all函数中注册,形成一个以

/** head of registered input format linked list */AVInputFormat *first_iformat = NULL;


为链表头的链表。ff_probe_input_buffer会通过ByteIOContext读取一段适配数据然后调用av_probe_input_format2找到正确的AVInputFormat(即通过遍历first_iformat)。

 

那么实现上面的任务通过修改ff_probe_input_buffer,在其中先解包,再让其调用av_probe_input_format2即可以达到部分欺骗FFMPEG的目的。但事实并不那么容易,找到正确的AVInputFormat后FFMPEG还会在av_open_input_stream中调用它的read_header来读取文件头部信息。这下麻烦大了,如果包装的文件原来是mp4格式的,那么得改mp4的AVInputFormat的read_header函数,如果是rmvb的……这个复杂性就不用说了。

 

回过头来看FFMPEG读文件流的过程,依赖于ByteIOContext,在url_fopen--->url_fdopen中生成了该对象并通过

if (init_put_byte(*s, buffer, buffer_size,                      (h->flags & URL_WRONLY || h->flags & URL_RDWR), h,                      url_read, url_write, url_seek) < 0)


设置了其内容。ByteIOContext定义如下:

/** * Bytestream IO Context. * New fields can be added to the end with minor version bumps. * Removal, reordering and changes to existing fields require a major * version bump. * sizeof(ByteIOContext) must not be used outside libav*. */typedef struct {    unsigned char *buffer;    int buffer_size;    unsigned char *buf_ptr, *buf_end;    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);    int64_t pos; /**< position in the file of the current buffer */    int must_flush; /**< true if the next seek should flush */    int eof_reached; /**< true if eof reached */    int write_flag;  /**< true if open for writing */    int is_streamed;    int max_packet_size;    unsigned long checksum;    unsigned char *checksum_ptr;    unsigned long (*update_checksum)(unsigned long checksum, const uint8_t *buf, unsigned int size);    int error;         ///< contains the error code or 0 if no error happened    int (*read_pause)(void *opaque, int pause);    int64_t (*read_seek)(void *opaque, int stream_index,                         int64_t timestamp, int flags);void *URLContext;} ByteIOContext;


通过分析可以看出,url_read就是其读取文件流的方式。这里注意到url_read的实现:

int url_read(URLContext *h, unsigned char *buf, int size){    int ret;    if (h->flags & URL_WRONLY)        return AVERROR(EIO);    ret = h->prot->url_read(h, buf, size);    return ret;}


可见读取文件流的过程又与具体的协议内容(URLContext)有关。其实在这里已经能够完成任务了,如下:

int fd = h->prot->url_get_file_handle(h);    // 其实就是h->priv_datalong pos = lseek(fd, 0, SEEK_CUR);__android_log_print(ANDROID_LOG_INFO, TAG, "url_read ftell pos: %d", pos);


pos代表文件流当前的位置,这样在哪个位置如何解包就一清二楚了。

 

下面还是简单说说URLContext与URLProtolcol。URLContext的定义如下:

/** * URL Context. * New fields can be added to the end with minor version bumps. * Removal, reordering and changes to existing fields require a major * version bump. * sizeof(URLContext) must not be used outside libav*. */typedef struct URLContext {#if LIBAVFORMAT_VERSION_MAJOR >= 53    const AVClass *av_class; ///< information for av_log(). Set by url_open().#endif    struct URLProtocol *prot;    int flags;    int is_streamed;  /**< true if streamed (no seek possible), default = false */    int max_packet_size;  /**< if non zero, the stream is packetized with this max packet size */    void *priv_data;    char *filename; /**< specified URL */} URLContext;


其中最重要的就是URLProtocol了,打开文件的过程中会根据文件的URL生成对应的URLProtocol及URLContext,才有了后面url_read中的ret = h->prot->url_read(h, buf, size);这儿的调用。URLProtocol才是真正负责协议中文件流数据读取的底层数据结构。下面举file协议(即本地文件)的例子。

URLProtocol file_protocol = {    "file",    file_open,    file_read,    file_write,    file_seek,    file_close,    .url_get_file_handle = file_get_handle,};


其中file_read:

static int file_read(URLContext *h, unsigned char *buf, int size){    int fd = (intptr_t) h->priv_data;    return read(fd, buf, size);}


可以看到了,就是Linux中read读文件。

原创粉丝点击