ffmpeg源码分析2
2017-10-21 23:49
253 查看
正文
基本上上一篇讲解了ffplayer的main函数的流程,主要讲解了编解码器以及触发器的注册。今天我们看下如何解码文件。开始结合源码分析。正文
stream_open这个货用来开启四个线程,专门处理视频流的。我们如果想完全了解清楚,就一点点的看。
static VideoState *stream_open(const char *filename, AVInputFormat *iformat) { VideoState *is; is = av_mallocz(sizeof(VideoState)); ......//初始化了很多东西,队列啦,变量啦等等。 is->read_tid = SDL_CreateThread(read_thread, "read_thread", is);//这是最核心的开启一个read_thread,通过这个启动解码线程和循环读取文件。 ...... return is;
这里逻辑很简单,就是初始化一个类型为VideoState 的结构体,其实这里很容易理解,因为c语言是面向过程的,所以最好有个全局的数据结构,可以方便区分逻辑,其实我们可以吧这个东西看成一个我们最关心的核心类,所有操作解码都是通过VideoState 来的,我们可以看下这个结构体
typedef struct VideoState { SDL_Thread *read_tid; //读取的线程号 AVInputFormat *iformat; // ...... AVFormatContext *ic; //这是一个从头文件中读取到的视频信息的一个结构体 ...... FrameQueue pictq; //解析后的帧数据 FrameQueue subpq; FrameQueue sampq; //解码器 Decoder auddec; Decoder viddec; Decoder subdec; //从文件读取到的每一帧的未经解析的数据。 PacketQueue audioq; enum ShowMode { SHOW_MODE_NONE = -1, SHOW_MODE_VIDEO = 0, SHOW_MODE_WAVES, SHOW_MODE_RDFT, SHOW_MODE_NB } show_mode; //不是核心, //控制显示的testure SDL_Texture *vis_texture; SDL_Texture *sub_texture; SDL_Texture *vid_texture; AVStream *subtitle_st; PacketQueue subtitleq; AVStream *video_st; PacketQueue videoq; char *filename; int width, height, xleft, ytop; int last_video_stream, last_audio_stream, last_subtitle_stream; SDL_cond *continue_read_thread; } VideoState;
这个结构体,基本包含了所有的内容,但是我不太清楚他问啥不作为一个静态变量存在,仅仅是作为一个局部变量。一直通过指针来传递,并且解析视频,貌似不支持同事播放多个视频的,所以基本没啥意义的。
static int read_thread(void *arg) { VideoState *is = arg; AVFormatContext *ic = NULL; ic = avformat_alloc_context(); err = avformat_open_input(&ic, is->filename, is->iformat, &format_opts); if (st_index[AVMEDIA_TYPE_VIDEO] >= 0) { ret = stream_component_open(is, st_index[AVMEDIA_TYPE_VIDEO]); } for (;;) { ......//这里机制很复杂,大概是如果读取一定数量的包后,就不在读取了,这里暂时不管 ret = av_read_frame(ic, pkt); packet_queue_put(&is->audioq, pkt); } }
前者半部分打开了三个线程用来解析字幕,视频音频,并且通过指令,解析到最适合的流,但是那里的代码,不是核心,并且比较复杂,就不在详细介绍,音频和字幕的解码,其实原理一样。都是调用一个函数,暂时不去关心。后半部分的一个无限循环,是读取packet,然后加入特定的对列,让解码线程解码。我们去看下到底如何解析视频的。
static int stream_component_open(VideoState *is, int stream_index) { avctx = avcodec_alloc_context3(NULL); ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);//获取解码的上下文,也就是 codec = avcodec_find_decoder(avctx->codec_id); //获取解码器。 decoder_init(&is->viddec, avctx, &is->videoq, is->continue_read_thread); if ((ret = decoder_start(&is->viddec, video_thread, is)) < 0) goto out; is->queue_attachments_req = 1; break; }
这里的代码是开始解码前的准备工作,上一步已经读取到文件的未经解析的packet文件,加入到一个对列,这里是解码钱的准备工作。通过
video_thread开启一个线程,可以完全解码。这里我们稍微浏览下
static int video_thread(void *arg) { for (;;) { ret = get_video_frame(is, frame); ......//不是核心,假如到一个队列中,用来渲染 }
static int get_video_frame(VideoState *is, AVFrame *frame) { int got_picture; if ((got_picture = decoder_decode_frame(&is->viddec, frame, NULL)) < 0) return -1; }
static int decoder_decode_frame(Decoder *d, AVFrame *frame, AVSubtitle *sub) { int ret = AVERROR(EAGAIN); for (;;) { AVPacket pkt; if (d->queue->serial == d->pkt_serial) { do { if (d->queue->abort_request) return -1; switch (d->avctx->codec_type) { case AVMEDIA_TYPE_VIDEO: ret = avcodec_receive_frame(d->avctx, frame); if (ret >= 0) { if (decoder_reorder_pts == -1) { frame->pts = frame->best_effort_timestamp; } else if (!decoder_reorder_pts) { frame->pts = frame->pkt_dts; } } break; case AVMEDIA_TYPE_AUDIO: ret = avcodec_receive_frame(d->avctx, frame); if (ret >= 0) { AVRational tb = (AVRational){1, frame->sample_rate}; if (frame->pts != AV_NOPTS_VALUE) frame->pts = av_rescale_q(frame->pts, av_codec_get_pkt_timebase(d->avctx), tb); else if (d->next_pts != AV_NOPTS_VALUE) frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb); if (frame->pts != AV_NOPTS_VALUE) { d->next_pts = frame->pts + frame->nb_samples; d->next_pts_tb = tb; } } break; } if (ret == AVERROR_EOF) { d->finished = d->pkt_serial; avcodec_flush_buffers(d->avctx); return 0; } if (ret >= 0) return 1; } while (ret != AVERROR(EAGAIN)); } do { if (d->queue->nb_packets == 0) SDL_CondSignal(d->empty_queue_cond); if (d->packet_pending) { av_packet_move_ref(&pkt, &d->pkt); d->packet_pending = 0; } else { if (packet_queue_get(d->queue, &pkt, 1, &d->pkt_serial) < 0) return -1; } } while (d->queue->serial != d->pkt_serial); if (pkt.data == flush_pkt.data) { avcodec_flush_buffers(d->avctx); d->finished = 0; d->next_pts = d->start_pts; d->next_pts_tb = d->start_pts_tb; } else { if (d->avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) { int got_frame = 0; ret = avcodec_decode_subtitle2(d->avctx, sub, &got_frame, &pkt); if (ret < 0) { ret = AVERROR(EAGAIN); } else { if (got_frame && !pkt.data) { d->packet_pending = 1; av_packet_move_ref(&d->pkt, &pkt); } ret = got_frame ? 0 : (pkt.data ? AVERROR(EAGAIN) : AVERROR_EOF); } } else { if (avcodec_send_packet(d->avctx, &pkt) == AVERROR(EAGAIN)) { av_log(d->avctx, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n"); d->packet_pending = 1; av_packet_move_ref(&d->pkt, &pkt); } } av_packet_unref(&pkt); } } }
解码完成,懒得解析了,有空大家看,一篇完整的视频编解码流程是:
相对简单的ffmpeg解码流程
后记
关于SDL的渲染,我没看。也懒得解释了,就不在详细介绍解码关键几个函数等有空再翻翻源码,争取搞懂。相关文章推荐
- ffmpeg源码分析--13.av_find_best_stream
- FFMPEG源码分析(1)--再版--持续更新
- FFMPEG源码分析(二)
- FFMPEG源码分析(1)--持续更新
- ffmpeg源码分析:transcode_init()函数
- ffmpeg学习六:avcodec_open2函数源码分析
- 最新版ffmpeg源码分析二:transcode()函数
- 最新版ffmpeg源码分析
- ffmpeg源码分析五:ffmpeg调用x264编码器的过程分析 (转5)
- FFmpeg源码分析之MpegTS.c
- ffmpeg源码分析之mpegts.c——精简分析
- ffmpeg源码分析之媒体打开过程
- FFMpeg 源码分析(2)avformat_network_init()
- FFMPEG 源码分析
- ffmpeg 源码分析
- ffmpeg源码分析之媒体打开过程
- ffmpeg源码分析:transcode_init()函数
- Ubuntu下为AndroidStudio编译并使用FFmpeg(三)源码分析
- ffmpeg 源码分析
- 最新版ffmpeg源码分析一:框架