音视频入门基础: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;
    }
相关推荐
涛涛讲AI1 分钟前
一段音频多段字幕,让音频能够流畅自然对应字幕 AI生成视频,扣子生成剪映视频草稿
人工智能·音视频·语音识别
lzptouch3 小时前
数据预处理(音频/图像/视频/文字)及多模态统一大模型输入方案
人工智能·音视频
casdfxx5 小时前
捡到h3开发板,做了个视频小车(二),御游追风plus做遥控器
音视频
mortimer5 小时前
FFmpeg 音画同步实践记录:从切片、变速到拼接,彻底搞定时间轴
ffmpeg
给大佬递杯卡布奇诺6 小时前
FFmpeg 基本API avcodec_send_packet函数内部调用流程分析
c++·ffmpeg·音视频
酌量7 小时前
从 ROS 订阅视频话题到本地可视化与 RTMP 推流全流程实战
经验分享·笔记·ffmpeg·音视频·ros
给大佬递杯卡布奇诺8 小时前
FFmpeg 基本API av_seek_frame函数内部调用流程分析
c++·ffmpeg·音视频
音视频牛哥12 小时前
从“小而美”到“大而强”:音视频直播SDK的技术进化逻辑
机器学习·计算机视觉·音视频·大牛直播sdk·人工智能+·rtsp播放器rtmp播放器·rtmp同屏推流
碎像12 小时前
ffmpeg下载和实战获取音视频时长
ffmpeg
空影星12 小时前
GridPlayer,一个好用的多视频同步播放器
python·flask·电脑·音视频