音视频入门基础: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;
    }
相关推荐
EasyCVR2 小时前
私有化部署视频平台EasyCVR宇视设备视频平台如何构建视频联网平台及升级视频转码业务?
大数据·网络·音视频·h.265
天空中的野鸟2 小时前
Android音频采集
android·音视频
计算机毕设孵化场3 小时前
计算机毕设-基于springboot的高校网上缴费综合务系统视频的设计与实现(附源码+lw+ppt+开题报告)
java·spring boot·计算机外设·音视频·课程设计·高校网上缴费综合务系统视频·计算机毕设ppt
lxkj_20248 小时前
使用线程局部存储解决ffmpeg中多实例调用下自定义日志回调问题
ffmpeg
简鹿办公10 小时前
如何提取某站 MV 视频中的音乐为 MP3 音频
音视频·简鹿视频格式转换器·视频提取mp3音频
yufengxinpian10 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
runing_an_min12 小时前
ffmpeg视频滤镜:替换部分帧-freezeframes
ffmpeg·音视频·freezeframes
ruizhenggang12 小时前
ffmpeg本地编译不容易发现的问题 — Error:xxxxx not found!
ffmpeg
runing_an_min14 小时前
ffmpeg视频滤镜:提取缩略图-framestep
ffmpeg·音视频·framestep
小曲曲15 小时前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频