音视频入门基础:FLV专题(17)——FFmpeg源码中,提取Video Tag的VIDEODATA的实现

一、引言

未加密的情况下,FLV文件中的一个Video Tag = Tag header + VideoTagHeader + VIDEODATA。在上一节《音视频入门基础:FLV专题(16)------FFmpeg源码中,解码Video Tag的VideoTagHeader的实现》中讲述了FFmpeg源码是怎样解码Video Tag的VideoTagHeader的。本文讲述FFmpeg源码在解码VideoTagHeader后,是怎样提取VIDEODATA的(以视频压缩编码格式为H.264为例)。

二、FFmpeg源码中,提取Video Tag的VIDEODATA的实现

从《音视频入门基础:FLV专题(15)------Video Tag简介》可以知道:当FLV文件的视频压缩编码格式为H.264并且未加密时,如果VideoTagHeader中的AVCPacketType的值为0,VIDEODATA为AVCDecoderConfigurationRecord;如果AVCPacketType的值为1,VIDEODATA包含一个或多个NALU;如果AVCPacketType的值为2,表示这段H.264码流结束,VIDEODATA没有数据。所以下面得分情况讨论。

(一)AVCPacketType的值为0

AVCPacketType的值为0时,VIDEODATA为AVCDecoderConfigurationRecord。flv_read_packet函数通过下面代码提取VIDEODATA:

cpp 复制代码
        if (type == 0 && (!st->codecpar->extradata || st->codecpar->codec_id == AV_CODEC_ID_AAC ||
            st->codecpar->codec_id == AV_CODEC_ID_H264 || st->codecpar->codec_id == AV_CODEC_ID_HEVC ||
            st->codecpar->codec_id == AV_CODEC_ID_AV1 || st->codecpar->codec_id == AV_CODEC_ID_VP9)) {
            AVDictionaryEntry *t;

            if (st->codecpar->extradata) {
                if ((ret = flv_queue_extradata(flv, s->pb, stream_type, size)) < 0)
                    return ret;
                ret = FFERROR_REDO;
                goto leave;
            }
            if ((ret = flv_get_extradata(s, st, size)) < 0)
                return ret;

            /* Workaround for buggy Omnia A/XE encoder */
            t = av_dict_get(s->metadata, "Encoder", NULL, 0);
            if (st->codecpar->codec_id == AV_CODEC_ID_AAC && t && !strcmp(t->value, "Omnia A/XE"))
                st->codecpar->extradata_size = 2;

            ret = FFERROR_REDO;
            goto leave;
        }

上面的代码块中,局部变量type存贮VideoTagHeader的AVCPacketType属性。当AVCPacketType值为1并且视频压缩编码格式为H.264并且还未获取avcC包装的H.264的extradata时,会执行:

cpp 复制代码
            if ((ret = flv_get_extradata(s, st, size)) < 0)
                return ret;

而flv_get_extradata函数内部会调用ff_get_extradata函数:

cpp 复制代码
static int flv_get_extradata(AVFormatContext *s, AVStream *st, int size)
{
//...
    if ((ret = ff_get_extradata(s, st->codecpar, s->pb, size)) < 0)
// ...
}

ff_get_extradata函数内部会调用ffio_read_size函数:

cpp 复制代码
int ff_get_extradata(void *logctx, AVCodecParameters *par, AVIOContext *pb, int size)
{
//...
    ret = ffio_read_size(pb, par->extradata, size);
//...
}

ffio_read_size函数内部会调用avio_read函数。而根据《FFmpeg源码:avio_read函数分析》可以知道,avio_read函数的作用是:首先尝试从AVIOContext输入缓冲区中读取数据,如果输入缓冲区中没有数据或者数据已被读完或者读完后还不够size个字节,通过文件描述符去读取本地媒体文件中的数据或者通过socket接收网络流中的数据,保存到形参buf指向的缓冲区中:

cpp 复制代码
int ffio_read_size(AVIOContext *s, unsigned char *buf, int size)
{
    int ret = avio_read(s, buf, size);
//...
}

所以下面flv_get_extradata语句的作用是:读取该Video Tag的AVCDecoderConfigurationRecord(extradata),将其存贮到s->streams[stream_index]->codecpar->extradata指向的缓冲区中。其中stream_index为该路视频流在FLV文件中的流索引,size为extradata的大小(以字节为单位):

cpp 复制代码
            if ((ret = flv_get_extradata(s, st, size)) < 0)
                return ret;

然后之后在flv_read_packet函数外部会通过ff_h264_decode_extradata函数解码上述提取出来的AVCDecoderConfigurationRecord(extradata),具体可以参考:《音视频入门基础:H.264专题(20)------FFmpeg源码中,解码AVCDecoderConfigurationRecord的实现》。

(二)AVCPacketType的值为1

AVCPacketType的值为1时,VIDEODATA包含一个或多个NALU。flv_read_packet函数通过下面代码提取VIDEODATA。即通过av_get_packet函数读取NALU数据(包含一个或多个NALU),保存到pkt->data指向的缓冲区中。关于av_get_packet函数可以参考:《FFmpeg源码:append_packet_chunked、av_get_packet、av_append_packet函数分析》。这样在执行下面的代码块后,pkt->data会得到该帧的实际的压缩后的H.264视频数据;pkt->dts会得到该帧的解码时间戳,解码时间戳来源于Tag header的Timestamp和TimestampExtended属性,具体可以参考:《音视频入门基础:FLV专题(8)------FFmpeg源码中,解码Tag header的实现》;pkt->pts会得到该帧的显示时间戳,显示时间戳等于dts + cts,具体可以参考:《音视频入门基础:FLV专题(16)------FFmpeg源码中,解码Video Tag的VideoTagHeader的实现​​​​​​​》:

cpp 复制代码
    ret = av_get_packet(s->pb, pkt, size);
    if (ret < 0)
        return ret;
    pkt->dts          = dts;
    pkt->pts          = pts == AV_NOPTS_VALUE ? dts : pts;
    pkt->stream_index = st->index;
    pkt->pos          = pos;

(三)AVCPacketType的值为2

AVCPacketType的值为2时,flv_read_packet函数通过下面代码提取VIDEODATA。由于AVCPacketType的值为2时表示这段H.264码流结束,VIDEODATA没有数据,所以直接goto leave不执行任何操作:

cpp 复制代码
    /* skip empty data packets */
    if (!size) {
        ret = FFERROR_REDO;
        goto leave;
    }
相关推荐
Hello.Reader1 小时前
FFmpeg 深度教程音视频处理的终极工具
ffmpeg·1024程序员节
runing_an_min1 小时前
ffmpeg视频滤镜:添加边框-drawbox
ffmpeg·音视频·边框·drawbox
wang_chao1183 小时前
FFMPEG+Qt 实时显示本机USB摄像头1080p画面以及同步录制mp4视频
qt·ffmpeg·音视频
EasyCVR6 小时前
宇视设备视频平台EasyCVR视频融合平台果园/鱼塘/养殖场/菜园有电没网视频监控方案
科技·音视频·视频监控·监控视频接入
ZPeng_csdn9 小时前
视频协议与封装格式
音视频
ai产品老杨9 小时前
深度学习模型量化原理
开发语言·人工智能·python·深度学习·安全·音视频
runing_an_min10 小时前
ffmpeg视频滤镜:网格-drawgrid
ffmpeg·音视频·网格·drawgrid
间彧15 小时前
FFmpeg推流器
ffmpeg
北京同三维影音设备16 小时前
同三维TK101控制键盘连接和使用视频说明书:控制键盘
计算机外设·音视频
生命几十年3万天17 小时前
ffmpeg常用命令
ffmpeg