前言
audio_thread线程主要负责解码音频数据。本文是把CONFIG_AVFILTER设置为0去掉滤镜功能后分析的。
一、audio_thread线程
去掉滤镜后的代码如下:
static int audio_thread(void *arg)
{
VideoState *is = arg;
AVFrame *frame = av_frame_alloc();
Frame *af;
int got_frame = 0;
AVRational tb;
int ret = 0;
if (!frame)
return AVERROR(ENOMEM);
do {
if ((got_frame = decoder_decode_frame(&is->auddec, frame, NULL)) < 0)
goto the_end;
if (got_frame) {
tb = (AVRational){1, frame->sample_rate};
if (!(af = frame_queue_peek_writable(&is->sampq)))
goto the_end;
af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
af->pos = frame->pkt_pos;
af->serial = is->auddec.pkt_serial;
af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
av_frame_move_ref(af->frame, frame);
frame_queue_push(&is->sampq);
}
} while (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF);
the_end:
av_frame_free(&frame);
return ret;
}
在audio_thread线程中,主要是do{}while()循环。从以上代码看,主要函数是解码函数decoder_decode_frame,以及操作队列的函数,frame_queue_peek_writable()和frame_queue_push()。代码功能可以分为两部分。第一部分:解码音频数据。第二部分:把解码的数据插入队列。
二、解码函数decoder_decode_frame()
decoder_decode_frame()用于解码,返回1代表解码成功,返回0代表读取到文件末尾,返回负数代表解码错误。详见链接
ffplay源码分析__解码函数decoder_decode_frame-CSDN博客
三、解码数据插入音频FrameQueue
1、首先判断队列是否可写
用frame_queue_peek_writable()获取可写数组的指针
static Frame *frame_queue_peek_writable(FrameQueue *f)
{
/* wait until we have space to put a new frame */
SDL_LockMutex(f->mutex);
while (f->size >= f->max_size &&
!f->pktq->abort_request) {
SDL_CondWait(f->cond, f->mutex);
}
SDL_UnlockMutex(f->mutex);
if (f->pktq->abort_request)
return NULL;
return &f->queue[f->windex];
}
从以上代码分析,中止请求的情况下,frame_queue_peek_writable()返回NULL代表不可写,直接跳转到the_end标签,并释放AVFrame的内存。
如果队列满了没有可写空间,程序会一直阻塞在SDL_CondWait()中
如果有可写空间,返回可写数组的指针,准备向队列插入数据。
2、插入数据
af->pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
af->pos = frame->pkt_pos;
af->serial = is->auddec.pkt_serial;
af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
av_frame_move_ref(af->frame, frame);
frame->pts代表该帧的显示时间戳,单位是时间基表示的,用av_q2d函数把frame->pts的时间戳单位转换成秒。以采样率为44100为例,时间基为AVRational{1,44100},每一份代表1/44100秒。
frame->pkt_pos是该frame在文件中的字节位置
is->auddec.pkt_serial是音频Decoder的序列号,在video_thread线程分析过序列号。把音频Decoder的序列号赋值给af->serial。
音频Decoder的序列号是在decoder_decode_frame()被赋值的,取自音频MyAVPacketList的序列号。所以此时Frame的序列号,Decoder的序列号,音频MyAVPacketList的序列号,以及音频PacketQueue的序列号都为1。
af->duration = av_q2d((AVRational){frame->nb_samples, frame->sample_rate});
音频的一帧数据中,一般每个通道包含1024个样本,af->duration代表一帧音频需要播放的时长为1024/44100=0.0232秒
接下来通过av_frame_move_ref(),拷贝frame的信息到af->frame中,至此Frame的赋值操作已完成。 然后通过frame_queue_push()更新队列的写索引和队列大小。 frame_queue_push()代码如下:
static void frame_queue_push(FrameQueue *f)
{
if (++f->windex == f->max_size)
f->windex = 0;
SDL_LockMutex(f->mutex);
f->size++;
SDL_CondSignal(f->cond);
SDL_UnlockMutex(f->mutex);
}
通过frame_queue_push()更新队列的写索引和队列大小。
四、总结
audio_thread主要功能是从音频PacketQueue中读取数据并解码,然后把解码后的数据插入到音频FrameQueue中。
audio_thread和video_thread基本流程一致,都用到解码函数decoder_decode_frame(),队列函数frame_queue_peek_writable()和frame_queue_push()。