视频解码
视频解码,读取数据包(packet),生成数据帧(frame),放入数据帧队列,用来输出音频和视频
解码流程
stream_component_open:
- 分配解码器上下文
avcodec_alloc_context3 - 将码流中的解码信息拷贝到上下文
avcodec_parameters_to_context - 查找解码器
avcodec_find_decoder - 关联上下文和解码器
avcodec_open2 - 音频解码:设置参数(采样率,声道数,布局通道等),获取steam索引和指针,初始化解码类Decoder,启动解码线程
- 视频解码:音频解码线程:获取steam索引和指针,初始化解码类Decoder, 启动解码线程
- 关闭上下文
int FFPlayer::stream_component_open(int stream_index)
{
AVCodecContext *avctx;
AVCodec *codec;
int sample_rate;
int nb_channels;
int64_t channel_layout;
int ret = 0;
// 判断stream_index是否合法
if (stream_index < 0 || stream_index >= ic->nb_streams)
return -1;
/* 为解码器分配一个编解码器上下文结构体 */
avctx = avcodec_alloc_context3(NULL);
if (!avctx)
return AVERROR(ENOMEM);
/* 将码流中的编解码器信息拷贝到新分配的编解码器上下文结构体 */
ret = avcodec_parameters_to_context(avctx, ic->streams[stream_index]->codecpar);
if (ret < 0)
goto fail;
// 设置pkt_timebase
avctx->pkt_timebase = ic->streams[stream_index]->time_base;
/* 根据codec_id查找解码器 */
codec = avcodec_find_decoder(avctx->codec_id);
if (!codec) {
av_log(NULL, AV_LOG_WARNING,
"No decoder could be found for codec %s\n", avcodec_get_name(avctx->codec_id));
ret = AVERROR(EINVAL);
goto fail;
}
if ((ret = avcodec_open2(avctx, codec, NULL)) < 0) {
goto fail;
}
switch (avctx->codec_type) {
case AVMEDIA_TYPE_AUDIO:
//从avctx(即AVCodecContext)中获取音频格式参数
sample_rate = avctx->sample_rate;; // 采样率
nb_channels = avctx->channels;; // 通道数
channel_layout= avctx->channel_layout;; // 通道布局
/* prepare audio output 准备音频输出*/
//调用audio_open打开sdl音频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区大小
if ((ret = audio_open( channel_layout, nb_channels, sample_rate, &audio_tgt)) < 0)
goto fail;
audio_hw_buf_size = ret;
audio_src = audio_tgt; //暂且将数据源参数等同于目标输出参数
//初始化audio_buf相关参数
audio_buf_size = 0;
audio_buf_index = 0;
audio_stream = stream_index; // 获取audio的stream索引
audio_st = ic->streams[stream_index]; // 获取audio的stream指针
// 初始化ffplay封装的音频解码器, 并将解码器上下文 avctx和Decoder绑定
auddec.decoder_init(avctx, &audioq);
// 启动音频解码线程
auddec.decoder_start(AVMEDIA_TYPE_AUDIO, "audio_thread", this);
// 允许音频输出
//play audio
SDL_PauseAudio(0);
break;
case AVMEDIA_TYPE_VIDEO:
video_stream = stream_index; // 获取video的stream索引
video_st = ic->streams[stream_index];// 获取video的stream指针
// // 初始化ffplay封装的视频解码器
viddec.decoder_init(avctx, &videoq); // is->continue_read_thread
// // 启动视频频解码线程
if ((ret = viddec.decoder_start(AVMEDIA_TYPE_VIDEO, "video_decoder", this)) < 0)
goto out;
break;
default:
break;
}
goto out;
fail:
avcodec_free_context(&avctx);
out:
return ret;
}
视频解码线程
video_thread:
- 分配解码帧
av_frame_alloc - 获取stream timebase,帧率
- 循环读取视频解码帧 decoder_decode_frame : 先读取一个数据帧avcodec_receive_frame,若无数据帧,先从数据包队列取数据packet_queue_get,之后发送数据avcodec_send_packet,同时释放包
- 将解码后的视频帧插入队列
queue_picture - 释放frame对应的数据
av_frame_unref
int Decoder::video_thread(void *arg)
{
std::cout << __FUNCTION__ << " into " << std::endl;
FFPlayer *is = (FFPlayer *)arg;
AVFrame *frame = av_frame_alloc(); // 分配解码帧
double pts; // pts
double duration; // 帧持续时间
int ret;
//1 获取stream timebase
AVRational tb = is->video_st->time_base; // 获取stream timebase
//2 获取帧率,以便计算每帧picture的duration
AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
if (!frame)
return AVERROR(ENOMEM);
for (;;) { // 循环取出视频解码的帧数据
// 3 获取解码后的视频帧
ret = get_video_frame(frame);
if (ret < 0)
goto the_end; //解码结束, 什么时候会结束
if (!ret) //没有解码得到画面, 什么情况下会得不到解后的帧
continue;
// 1/25 = 0.04秒
// 4 计算帧持续时间和换算pts值为秒
// 1/帧率 = duration 单位秒, 没有帧率时则设置为0, 有帧率帧计算出帧间隔
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
// 根据AVStream timebase计算出pts值, 单位为秒
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb); // 单位为秒
// 5 将解码后的视频帧插入队列
ret = queue_picture(&is->pictq, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial_);
// 6 释放frame对应的数据
av_frame_unref(frame);
if (ret < 0) // 返回值小于0则退出线程
goto the_end;
}
the_end:
std::cout << __FUNCTION__ << " leave " << std::endl;
av_frame_free(&frame);
return 0;
}
int Decoder::get_video_frame(AVFrame *frame)
{
int got_picture;
// 1. 获取解码后的视频帧
if ((got_picture = decoder_decode_frame(frame)) < 0) {
return -1; // 返回-1意味着要退出解码线程, 所以要分析decoder_decode_frame什么情况下返回-1
}
if (got_picture) {
// 2. 分析获取到的该帧是否要drop掉, 该机制的目的是在放入帧队列前先drop掉过时的视频帧
// frame->sample_aspect_ratio = av_guess_sample_aspect_ratio(is->ic, is->video_st, frame);
}
return got_picture;
}
int Decoder::decoder_decode_frame(AVFrame *frame)
{
int ret = AVERROR(EAGAIN);
for (;;) {
AVPacket pkt;
do { // 第一个循环 先把codec里的frame 全部读取
if (queue_->abort_request) // decoder_abort调用的时候 触发queue_->abort_request为1
return -1; // 是否请求退出
switch (avctx_->codec_type) {
case AVMEDIA_TYPE_VIDEO:
ret = avcodec_receive_frame(avctx_, frame);
//printf("frame pts:%ld, dts:%ld\n", frame->pts, frame->pkt_dts);
if (ret >= 0) {
// if (decoder_reorder_pts == -1) {
// frame->pts = frame->best_effort_timestamp;
// } else if (!decoder_reorder_pts) {
// frame->pts = frame->pkt_dts;
// }
} else {
char errStr[256] = { 0 };
av_strerror(ret, errStr, sizeof(errStr));
printf("video dec:%s\n", errStr);
}
break;
case AVMEDIA_TYPE_AUDIO:
ret = avcodec_receive_frame(avctx_, frame);
if (ret >= 0) {
AVRational tb = (AVRational){1, frame->sample_rate}; //
if (frame->pts != AV_NOPTS_VALUE) {
// 如果frame->pts正常则先将其从pkt_timebase转成{1, frame->sample_rate}
// pkt_timebase实质就是stream->time_base
frame->pts = av_rescale_q(frame->pts, avctx_->pkt_timebase, tb);
}
// else if (d->next_pts != AV_NOPTS_VALUE) {
// // 如果frame->pts不正常则使用上一帧更新的next_pts和next_pts_tb
// // 转成{1, frame->sample_rate}
// frame->pts = av_rescale_q(d->next_pts, d->next_pts_tb, tb);
// }
}else {
char errStr[256] = { 0 };
av_strerror(ret, errStr, sizeof(errStr));
printf("audio dec:%s, ret:%d,%d\n", errStr, ret, AVERROR(EAGAIN));
}
break;
}
// 1.3. 检查解码是否已经结束,解码结束返回0
if (ret == AVERROR_EOF) {
printf("avcodec_flush_buffers %s(%d)\n", __FUNCTION__, __LINE__);
avcodec_flush_buffers(avctx_);
return 0;
}
// 1.4. 正常解码返回1
if (ret >= 0)
return 1; // 获取到一帧frame
}while (ret != AVERROR(EAGAIN)); // 1.5 没帧可读时ret返回EAGIN,需要继续送packet
// 在目前这个版本我们还不去检测播放序列的问题
// 2 如果上面的循环获取到了frame这里不会被执行,第二个循环,主要是读取packet送给解码器
// do { // 在目前这个版本我们还不去检测播放序列的问题
// if (queue_->nb_packets == 0) // 没有数据可读
// SDL_CondSignal(d->empty_queue_cond);// 通知read_thread放入packet
// 2.3 阻塞式读取packet
if (packet_queue_get(queue_, &pkt, 1, &pkt_serial_) < 0)
return -1;
// } while (d->queue->serial != d->pkt_serial);// 如果不是同一播放序列(流不连续)则继续读取
if (avcodec_send_packet(avctx_, &pkt) == AVERROR(EAGAIN)) {
av_log(avctx_, AV_LOG_ERROR, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
// 先暂存这个pkt
}
av_packet_unref(&pkt); // 一定要自己去释放音视频数据
}
}
音频解码线程
audio_thread:
- 分配解码帧
av_frame_alloc - 读取解码帧: decoder_decode_frame,
- 获取可写帧,转换帧的时间戳,帧长 ,将解码帧frame赋值给可写帧
frame_queue_peek_writable
frame_queue_peek_writable - 将可写帧放入帧队列
frame_queue_push - 释放解码帧
av_frame_free
int Decoder::audio_thread(void *arg)
{
std::cout << __FUNCTION__ << " into " << std::endl;
FFPlayer *is = (FFPlayer *)arg;
AVFrame *frame = av_frame_alloc(); // 分配解码帧
Frame *af;
int got_frame = 0; // 是否读取到帧
AVRational tb; // timebase
int ret = 0;
if (!frame)
return AVERROR(ENOMEM);
do {
// 1. 读取解码帧
if ((got_frame = decoder_decode_frame(frame)) < 0) // 是否获取到一帧数据
goto the_end; // < =0 abort
if (got_frame) {
tb = (AVRational){1, frame->sample_rate}; // 设置为sample_rate为timebase
// 2. 获取可写Frame
if (!(af = frame_queue_peek_writable(&is->sampq))) // 获取可写帧
goto the_end;
// 3. 设置Frame并放入FrameQueue
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:
std::cout << __FUNCTION__ << " leave " << std::endl;
av_frame_free(&frame);
return ret;
}
数据帧队列
类型定义
用于缓存解码后的数据:
typedef struct Frame {
AVFrame *frame; // 指向数据帧
double pts; // 时间戳,单位为秒
double duration; // 该帧持续时间,单位为秒
int width; // 图像宽度
int height; // 图像高读
int format; // 对于图像为(enum AVPixelFormat)
} Frame;
帧队列:
typedef struct FrameQueue {
Frame queue[FRAME_QUEUE_SIZE]; // FRAME_QUEUE_SIZE 最大size, 数字太大时会占用大量的内存,需要注意该值的设置
int rindex; // 读索引。待播放时读取此帧进行播放,播放后此帧成为上一帧
int windex; // 写索引
int size; // 当前总帧数
int max_size; // 可存储最大帧数
SDL_mutex *mutex; // 互斥量
SDL_cond *cond; // 条件变量
PacketQueue *pktq; // 数据包缓冲队列
} FrameQueue;
数据帧队列api
- 初始化FrameQueue
int frame_queue_init(FrameQueue *f, PacketQueue *pktq, int max_size)
{
int i;
memset(f, 0, sizeof(FrameQueue));
if (!(f->mutex = SDL_CreateMutex())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateMutex(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);
}
if (!(f->cond = SDL_CreateCond())) {
av_log(NULL, AV_LOG_FATAL, "SDL_CreateCond(): %s\n", SDL_GetError());
return AVERROR(ENOMEM);
}
f->pktq = pktq;
f->max_size = FFMIN(max_size, FRAME_QUEUE_SIZE);
for (i = 0; i < f->max_size; i++)
if (!(f->queue[i].frame = av_frame_alloc())) // 分配AVFrame结构体
return AVERROR(ENOMEM);
return 0;
}
- 获取帧
int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block, int *serial)
{
MyAVPacketList *pkt1;
int ret;
SDL_LockMutex(q->mutex); // 加锁
for (;;) {
if (q->abort_request) {
ret = -1;
break;
}
pkt1 = q->first_pkt; //MyAVPacketList *pkt1; 从队头拿数据
if (pkt1) { //队列中有数据
q->first_pkt = pkt1->next; //队头移到第二个节点
if (!q->first_pkt)
q->last_pkt = NULL;
q->nb_packets--; //节点数减1
q->size -= pkt1->pkt.size + sizeof(*pkt1); //cache大小扣除一个节点
q->duration -= pkt1->pkt.duration; //总时长扣除一个节点
//返回AVPacket,这里发生一次AVPacket结构体拷贝,AVPacket的data只拷贝了指针
*pkt = pkt1->pkt;
if (serial) //如果需要输出serial,把serial输出
*serial = pkt1->serial;
av_free(pkt1); //释放节点内存,只是释放节点,而不是释放AVPacket
ret = 1;
break;
} else if (!block) { //队列中没有数据,且非阻塞调用
ret = 0;
break;
} else { //队列中没有数据,且阻塞调用
//这里没有break。for循环的另一个作用是在条件变量满足后重复上述代码取出节点
SDL_CondWait(q->cond, q->mutex);
}
}
SDL_UnlockMutex(q->mutex); // 释放锁
return ret;
}
- 更新写指针
// 更新写指针
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); // 当_readable在等待时则可以唤醒
SDL_UnlockMutex(f->mutex);
}
- 获取可写指针
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 *frame_queue_peek_readable(FrameQueue *f)
{
/* wait until we have a readable a new frame */
SDL_LockMutex(f->mutex);
while (f->size <= 0 &&
!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->rindex) % f->max_size];
}
- 更新写指针
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); // 当_readable在等待时则可以唤醒
SDL_UnlockMutex(f->mutex);
}
- 释放当前frame,并更新读索引rinde
void frame_queue_next(FrameQueue *f)
{
frame_queue_unref_item(&f->queue[f->rindex]);
if (++f->rindex == f->max_size)
f->rindex = 0;
SDL_LockMutex(f->mutex);
f->size--;
SDL_CondSignal(f->cond);
SDL_UnlockMutex(f->mutex);
}
- 发送信号
void frame_queue_signal(FrameQueue *f)
{
SDL_LockMutex(f->mutex);
SDL_CondSignal(f->cond);
SDL_UnlockMutex(f->mutex);
}
- 销毁
void frame_queue_destory(FrameQueue *f)
{
int i;
for (i = 0; i < f->max_size; i++) {
Frame *vp = &f->queue[i];
// 释放对vp->frame中的数据缓冲区的引用,注意不是释放frame对象本身
frame_queue_unref_item(vp);
// 释放vp->frame对象
av_frame_free(&vp->frame);
}
SDL_DestroyMutex(f->mutex);
SDL_DestroyCond(f->cond);
}