简化ffplay的分析
2017-01-23 15:46
357 查看
这份代码来自:http://www.cnblogs.com/mcodec/articles/1933754.htm
感谢这位作者. ffplay简化版,剪了百分之80的代码.入门还是不错的,并且可以在vc 6.0编译.但是这个版本很老了,应该是0版本,现在都是3版本了.代码基本都变动了.但是万变不离其宗.
总结写在前面,这一部分是在本篇结束后写的:
音视频的真正输出是SDL,SDL是Simple DirectMedia Layer(简易直控媒体层)的缩写。它是一个跨平台的多媒体库,以用于直接控制底层的多媒体硬件的接口。所以弄清楚的是怎么使用SDL即可.
ffplay是多线程模式:主线程wait,解析数据线程,audio线程,vedio线程
ffmpeg好多协议,主要有这三个: proto:数据来源方式,主要是文件的,网络的. format:封装协议, decode:编解码.
从初始化开始.
出现了一个问题:由于有很多编解码,很多容器,很多数据来源方式, 靠什么来确定他们的呢?一个一个的来,数据来源方式如何选择:
由于看的是简化版,所以直接读的文件,没有区分file proto和net proto.
有一个protocol列表.
在av_open_input_file函数中使用到了 url_fopen,url_fread,url_fclose,url_fseek. 分析一下open函数,找open中找到了proto.
url_fopen主要逻辑在于url_open,其他都在初始化.
url_open函数简化了.只有file_proto.在这个函数中找到了proto.
上述逻辑不是很清晰,可能是看的是简化版,基本指定了file.
net和file怎么来区分呢?需要进一步分析.
有一个format列表,通过遍历列表,可以获取到format.
定义AVInputFormat,之后加入到format列表中,通过name来区分各个format.
列表有了,怎么来选出来呢?从一个函数开始 av_open_input_file ,简化并去掉错误处理,就是如下.
探测函数:遍历formt列表,每一个进行探测,并比较socre,得到最高的fmt.
avi的read_probe函数:这个就简单了,直接判断前几个字符.
format出来后,还直接解析了头部.av_open_input_stream,简化后,设置priv_data,执行fmt.read_header
fmt的read_header函数,这个就比较复杂啦!很长,都无法简化.
不用列出代码了,解析header,获取到了vedio和audio的一些配置信息.
把msrle放到codec列表中.
定义msrle decoder.
这里面有一个id = CODEC_ID_MSRLE, 但是这个id是如何确定的呢?
id获取函数:
看一下使用方式:
通过vedio tag 来获取到的.所以可以确定:
每一个封装,都会有自定义来判断方式.
先注册proto,format,decoder.
初始化SDL
open文件
loop
1,2步不用看了.先看4步,因为第4步简单,没做啥事.主线程等待SDL event,特别是exit.
再来第三步,创建了一个解析线程而已.
解析线程的入口函数:
解析文件,启动子线程,并把解析出来的音视频放入队列中.
一共有线程:
主线程: 起到一个监听事件的作用
解析线程: 解析文件,获取音视频包,并方式到queue中.
音频线程: 通过queue和解析线程交互.
视频线程: 通过queue和解析线程交互.
线程结构完成了,看一下视频线程的具体逻辑.
video thread从queue中获取到pkg,开始decode,之后显示.
display靠的是SDL,这个具体逻辑另开一篇吧.这个比较复杂.主要看decode,调用了decoder的decode.每个编解码都会有自己的decode.
好了,整体框架应该是理清啦.
其实还没有到重点.重点是编解码逻辑,还是显示逻辑.
下一步分析一下显示逻辑.
感谢这位作者. ffplay简化版,剪了百分之80的代码.入门还是不错的,并且可以在vc 6.0编译.但是这个版本很老了,应该是0版本,现在都是3版本了.代码基本都变动了.但是万变不离其宗.
总结写在前面,这一部分是在本篇结束后写的:
音视频的真正输出是SDL,SDL是Simple DirectMedia Layer(简易直控媒体层)的缩写。它是一个跨平台的多媒体库,以用于直接控制底层的多媒体硬件的接口。所以弄清楚的是怎么使用SDL即可.
ffplay是多线程模式:主线程wait,解析数据线程,audio线程,vedio线程
ffmpeg好多协议,主要有这三个: proto:数据来源方式,主要是文件的,网络的. format:封装协议, decode:编解码.
从初始化开始.
void av_register_all(void) { static int inited = 0; if (inited != 0) return ; inited = 1; //数字信号处理(Digital Signal Processing,简称DSP) avcodec_init(); //decoder avcodec_register_all(); //format avidec_init(); //poto register_protocol(&file_protocol); }
出现了一个问题:由于有很多编解码,很多容器,很多数据来源方式, 靠什么来确定他们的呢?一个一个的来,数据来源方式如何选择:
如果确定IO呢?
获取数据有很多方式,最常用的网络和文件,不知道ffmpeg考虑串口没?由于看的是简化版,所以直接读的文件,没有区分file proto和net proto.
有一个protocol列表.
typedef struct URLProtocol { const char *name; int(*url_open)(URLContext *h, const char *filename, int flags); int(*url_read)(URLContext *h, unsigned char *buf, int size); int(*url_write)(URLContext *h, unsigned char *buf, int size); offset_t(*url_seek)(URLContext *h, offset_t pos, int whence); int(*url_close)(URLContext *h); struct URLProtocol *next; } URLProtocol; URLProtocol file_protocol = { "file", file_open, file_read, file_write, file_seek, file_close, }; register_protocol(&file_protocol);
在av_open_input_file函数中使用到了 url_fopen,url_fread,url_fclose,url_fseek. 分析一下open函数,找open中找到了proto.
url_fopen主要逻辑在于url_open,其他都在初始化.
int url_fopen(ByteIOContext *s, const char *filename, int flags) { URLContext *h; uint8_t *buffer; int buffer_size, max_packet_size; int err; err = url_open(&h, filename, flags); ... return 0; }
url_open函数简化了.只有file_proto.在这个函数中找到了proto.
int url_open(URLContext **puc, const char *filename, int flags) { URLContext *uc; URLProtocol *up; const char *p; char proto_str[128], *q; int err; p = filename; q = proto_str; file_proto: strcpy(proto_str, "file"); //在protocol列表中找到proto up = first_protocol; while (up != NULL) { if (!strcmp(proto_str, up->name)) goto found; up = up->next; } err = - ENOENT; goto fail; found: uc = av_malloc(sizeof(URLContext) + strlen(filename)); strcpy(uc->filename, filename); uc->prot = up; uc->flags = flags; uc->max_packet_size = 0; // default: stream file err = up->url_open(uc, filename, flags); //真正的open函数,具体逻辑简单,不用跟了. *puc = uc; return 0; }
上述逻辑不是很清晰,可能是看的是简化版,基本指定了file.
net和file怎么来区分呢?需要进一步分析.
如果确定format呢?
有很多封装,flv的,avi等等.怎么来确定一个封装格式的.有一个format列表,通过遍历列表,可以获取到format.
void av_register_input_format(AVInputFormat *format) { AVInputFormat **p; p = &first_iformat; while (*p != NULL) p = &(*p)->next; *p = format; format->next = NULL; }
定义AVInputFormat,之后加入到format列表中,通过name来区分各个format.
typedef struct AVInputFormat { const char *name; int priv_data_size; int(*read_probe)(AVProbeData*); int(*read_header)(struct AVFormatContext *, AVFormatParameters *ap); int(*read_packet)(struct AVFormatContext *, AVPacket *pkt); int(*read_close)(struct AVFormatContext*); const char *extensions; struct AVInputFormat *next; } AVInputFormat; AVInputFormat avi_iformat = { "avi", sizeof(AVIContext), avi_probe, avi_read_header, avi_read_packet, avi_read_close, }; av_register_input_format(&avi_iformat);
列表有了,怎么来选出来呢?从一个函数开始 av_open_input_file ,简化并去掉错误处理,就是如下.
int av_open_input_file(AVFormatContext **ic_ptr, const char *filename, //文件名 AVInputFormat *fmt, int buf_size, AVFormatParameters *ap) { int err, must_open_file, file_opened, probe_size; AVProbeData probe_data, *pd = &probe_data; ByteIOContext pb1, *pb = &pb1; file_opened = 0; pd->filename = ""; if (filename) pd->filename = filename; pd->buf = NULL; pd->buf_size = 0; must_open_file = 1; url_fopen(pb, filename, URL_RDONLY) //打开文件 file_opened = 1; //开始探测 for (probe_size = PROBE_BUF_MIN; probe_size <= PROBE_BUF_MAX && !fmt; probe_size <<= 1) { pd->buf = av_realloc(pd->buf, probe_size); pd->buf_size = url_fread(pb, pd->buf, probe_size); fmt = av_probe_input_format(pd, 1); //开始探测 } av_freep(&pd->buf); //探测结束后,得free掉. err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap); //解析文件:解析文件头部 return 0; }
探测函数:遍历formt列表,每一个进行探测,并比较socre,得到最高的fmt.
AVInputFormat *av_probe_input_format(AVProbeData *pd, int is_opened) { AVInputFormat *fmt1, *fmt; int score, score_max; fmt = NULL; score_max = 0; //遍历format列表,每个都探测,并打分 for (fmt1 = first_iformat; fmt1 != NULL; fmt1 = fmt1->next) { score = 0; if (fmt1->read_probe) { score = fmt1->read_probe(pd); //打分,很调皮嘛 } else if (fmt1->extensions) //后缀名 { if (match_ext(pd->filename, fmt1->extensions)) score = 50; } if (score > score_max) { score_max = score; fmt = fmt1; } } return fmt; //返回的是打分最高的fmt. }
avi的read_probe函数:这个就简单了,直接判断前几个字符.
static int avi_probe(AVProbeData *p) { if (p->buf_size <= 32) // check file header return 0; if (p->buf[0] == 'R' && p->buf[1] == 'I' && p->buf[2] == 'F' && p->buf[3] == 'F' && p->buf[8] == 'A' && p->buf[9] == 'V' && p->buf[10] == 'I'&& p->buf[11] == ' ') return AVPROBE_SCORE_MAX; //这个可是100分. else return 0; }
format出来后,还直接解析了头部.av_open_input_stream,简化后,设置priv_data,执行fmt.read_header
int av_open_input_stream(AVFormatContext **ic_ptr, ByteIOContext *pb, const char *filename, AVInputFormat *fmt, //这个就是探测出的format AVFormatParameters *ap) { int err; AVFormatContext *ic; ic = av_mallocz(sizeof(AVFormatContext)); ic->iformat = fmt; if (pb) ic->pb = *pb; //priv data是什么呢? 在avi中就是:AVIContext ic->priv_data = av_mallocz(fmt->priv_data_size); err = ic->iformat->read_header(ic, ap); *ic_ptr = ic; return 0; }
fmt的read_header函数,这个就比较复杂啦!很长,都无法简化.
不用列出代码了,解析header,获取到了vedio和audio的一些配置信息.
如何确定编解码呢?
codecer有一个列表,通过遍历列表,可以获取到codecer.void register_avcodec(AVCodec *format) { AVCodec **p; p = &first_avcodec; while (*p != NULL) p = &(*p)->next; *p = format; format->next = NULL; }
把msrle放到codec列表中.
register_avcodec(&msrle_decoder);
定义msrle decoder.
AVCodec msrle_decoder = { "msrle", CODEC_TYPE_VIDEO, CODEC_ID_MSRLE, sizeof(MsrleContext), msrle_decode_init, NULL, msrle_decode_end, msrle_decode_frame };
这里面有一个id = CODEC_ID_MSRLE, 但是这个id是如何确定的呢?
typedef struct { int id; unsigned int tag; } CodecTag; const CodecTag codec_bmp_tags[] = { {CODEC_ID_MSRLE, MKTAG('m', 'r', 'l', 'e')}, {CODEC_ID_MSRLE, MKTAG(0x1, 0x0, 0x0, 0x0)}, {CODEC_ID_NONE, 0}, };
id获取函数:
enum CodecID codec_get_id(const CodecTag *tags, unsigned int tag) { while (tags->id != CODEC_ID_NONE) { if (toupper((tag >> 0) &0xFF) == toupper((tags->tag >> 0) &0xFF) && toupper((tag >> 8) &0xFF) == toupper((tags->tag >> 8) &0xFF) && toupper((tag >> 16)&0xFF) == toupper((tags->tag >> 16)&0xFF) && toupper((tag >> 24)&0xFF) == toupper((tags->tag >> 24)&0xFF)) return tags->id; tags++; } return CODEC_ID_NONE; }
看一下使用方式:
case CODEC_TYPE_VIDEO: // BITMAPINFOHEADER get_le32(pb); // size st->actx->width = get_le32(pb); st->actx->height = get_le32(pb); get_le16(pb); // panes st->actx->bits_per_sample = get_le16(pb); // depth tag1 = get_le32(pb); ...... st->actx->codec_type = CODEC_TYPE_VIDEO; st->actx->codec_id = codec_get_id(codec_bmp_tags, tag1);
通过vedio tag 来获取到的.所以可以确定:
每一个封装,都会有自定义来判断方式.
整体架构
简化版的ffplay,直接读取的是file文件,简单清晰明了.分4步:先注册proto,format,decoder.
初始化SDL
open文件
loop
int main(int argc, char **argv) { const char *input_filename = "./clocktxt_320.avi"; //直接指定了文件名 av_register_all(); int flags = SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER; SDL_Init(flags) SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE); SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE); SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE); SDL_EventState(SDL_USEREVENT, SDL_IGNORE); cur_stream = stream_open(input_filename); event_loop(); return 0; }
1,2步不用看了.先看4步,因为第4步简单,没做啥事.主线程等待SDL event,特别是exit.
void event_loop(void) { SDL_Event event; for (;;) { SDL_WaitEvent(&event); switch (event.type) { case SDL_KEYDOWN: switch (event.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_q: do_exit(); break; default: break; } break; case SDL_QUIT: case FF_QUIT_EVENT: do_exit(); break; default: break; } } }
再来第三步,创建了一个解析线程而已.
static VideoState *stream_open(const char *filename) { VideoState *is; is = av_mallocz(sizeof(VideoState)); pstrcpy(is->filename, sizeof(is->filename), filename); //create thread is->parse_tid = SDL_CreateThread(decode_thread, is); return is; }
解析线程的入口函数:
解析文件,启动子线程,并把解析出来的音视频放入队列中.
static int decode_thread(void *arg) { VideoState *is = arg; AVFormatContext *ic; int err, i, ret, video_index, audio_index; AVPacket pkt1, *pkt = &pkt1; AVFormatParameters params, *ap = ¶ms; int n; int flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE; video_index = - 1; audio_index = - 1; is->video_stream = - 1; is->audio_stream = - 1; n = 0; memset(ap, 0, sizeof(*ap)); //1 这个函数分析过,获取了proto,format和decoder. err = av_open_input_file(&ic, is->filename, NULL, 0, ap); is->ic = ic; //2 启动音视频的线程 if (audio_index >= 0) stream_component_open(is, audio_index); //启动解析audio的线程 if (video_index >= 0) stream_component_open(is, video_index); //启动解析vedio的线程 //3 read线程:读文件,并写入到文件中. for (;;) { if (is->audioq.size > MAX_AUDIOQ_SIZE || is->videoq.size > MAX_VIDEOQ_SIZE || url_feof(&ic->pb)) { SDL_Delay(10); // if the queue are full, no need to read more,wait 10 ms continue; } ret = av_read_packet(ic, pkt); //av_read_frame(ic, pkt); if (pkt->stream_index == is->audio_stream) { packet_queue_put(&is->audioq, pkt,"audio"); } else if (pkt->stream_index == is->video_stream) { packet_queue_put(&is->videoq, pkt,"video"); } else { av_free_packet(pkt); } } return 0; }
一共有线程:
主线程: 起到一个监听事件的作用
解析线程: 解析文件,获取音视频包,并方式到queue中.
音频线程: 通过queue和解析线程交互.
视频线程: 通过queue和解析线程交互.
线程结构完成了,看一下视频线程的具体逻辑.
static int stream_component_open(VideoState *is, int stream_index) { AVFormatContext *ic = is->ic; AVCodecContext *enc; AVCodec *codec; SDL_AudioSpec wanted_spec, spec; // 1 知道codecoder enc = ic->streams[stream_index]->actx; codec = avcodec_find_decoder(enc->codec_id); if (!codec || avcodec_open(enc, codec) < 0) return - 1; switch (enc->codec_type) { case CODEC_TYPE_AUDIO: ... break; case CODEC_TYPE_VIDEO: is->video_stream = stream_index; is->video_st = ic->streams[stream_index]; is->frame_last_delay = is->video_st->frame_last_delay; packet_queue_init(&is->videoq); is->video_tid = SDL_CreateThread(video_thread, is); //video线程启动 break; default: break; } return 0; }
video thread从queue中获取到pkg,开始decode,之后显示.
static int video_thread(void *arg) { VideoState *is = arg; AVPacket pkt1, *pkt = &pkt1; int len1, got_picture; double pts = 0; AVFrame *frame = av_malloc(sizeof(AVFrame)); memset(frame, 0, sizeof(AVFrame)); alloc_picture(is); for (;;) { //获取到vedio pkt if (packet_queue_get(&is->videoq, pkt, 1,"video") < 0) break; SDL_LockMutex(is->video_decoder_mutex); len1 = avcodec_decode_video(is->video_st->actx, frame, &got_picture, pkt->data, pkt->size); SDL_UnlockMutex(is->video_decoder_mutex); if (pkt->dts != AV_NOPTS_VALUE) pts = av_q2d(is->video_st->time_base) *pkt->dts; if (got_picture) { video_display(is, frame, pts); } av_free_packet(pkt); } the_end: av_free(frame); return 0; }
display靠的是SDL,这个具体逻辑另开一篇吧.这个比较复杂.主要看decode,调用了decoder的decode.每个编解码都会有自己的decode.
int avcodec_decode_video(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, uint8_t *buf, int buf_size) { int ret; *got_picture_ptr = 0; if (buf_size) { fprintf(stderr, "avcodec_decode_video buf_size:%d,frame_number:%d\n",buf_size,avctx->frame_number); ret = avctx->codec->decode(avctx, picture, got_picture_ptr, buf, buf_size); if (*got_picture_ptr) avctx->frame_number++; } else ret = 0; return ret; }
好了,整体框架应该是理清啦.
其实还没有到重点.重点是编解码逻辑,还是显示逻辑.
下一步分析一下显示逻辑.
相关文章推荐
- 应用 Rational 工具简化基于 J2EE的项目第 4 部分 : 分析和工具的进展
- 应用Rational工具简化基于J2EE项目(四)分析和工具的进展
- 如何采用简化方法进行需求分析
- 敏捷开发绩效管理之六:敏捷开发生产率(中)(功能点分析,FPA,简化的功能点)
- 敏捷开发绩效管理之七:敏捷开发生产率(下)(简化功能点分析,NESMA,两级简化)
- 敏捷开发绩效管理之六:敏捷开发生产率(中)(功能点分析,FPA,简化的功能点)
- 使用typedef简化声明的实例分析
- 敏捷开发绩效管理之六:敏捷开发生产率(中)(功能点分析,FPA,简化的功能点)
- 敏捷开发绩效管理之六:敏捷开发生产率(中)(功能点分析,FPA,简化的功能点)
- 应用Rational工具简化基于J2EE项目(四)分析和工具的进展
- microwindows代码分析 (一)c/s模型的简化
- 如何采用简化方法进行需求分析
- 基于FFPLAY与SDL的h.264的播放器的简化与移植
- 简化原型法进行需求分析
- 采用简化原型法进行需求分析
- 应用 Rational 工具简化基于 J2EE的项目第 4 部分 : 分析和工具的进展
- JUnit设计模式分析及简化的JUnit代码
- 分析Linux Oracle最简化最次要的步调-1
- 敏捷开发绩效管理之七:敏捷开发生产率(下)(简化功能点分析,NESMA,两级简化)
- 敏捷开发绩效管理之七:敏捷开发生产率(下)(简化功能点分析,NESMA,两级简化)