音视频入门基础: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;
    }
相关推荐
Say-hai4 小时前
音视频入门知识(七):时间戳及其音视频播放原理
音视频
Java搬砖组长4 小时前
youtube下载的视频怎么保存到本地
音视频
科技小E4 小时前
国标GB28181设备管理软件EasyGBS:P2P远程访问故障排查指南(设备端)
网络协议·智能路由器·音视频·p2p
神仙别闹5 小时前
基于C#实现的(WinForm)模拟操作系统文件管理系统
java·git·ffmpeg
Say-hai6 小时前
音视频入门知识(二)、图像篇
音视频
Fre丸子_18 小时前
ffmpeg之播放一个yuv视频
ffmpeg·音视频
9527华安18 小时前
FPGA多路MIPI转FPD-Link视频缩放拼接显示,基于IMX327+FPD953架构,提供2套工程源码和技术支持
fpga开发·架构·音视频
catmes20 小时前
设置浏览器声音或视频的自动播放策略
chrome·音视频·edge浏览器
yinqinggong20 小时前
从源码编译支持FFmpeg的OpenCV
opencv·ffmpeg
冰山一脚201321 小时前
ffmpeg添加sps,pps
ffmpeg