基本上上一篇讲解了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的渲染,我没看。也懒得解释了,就不在详细介绍解码关键几个函数等有空再翻翻源码,争取搞懂。