FFmpeg从入门到精通:SEI那些事(3)
具体构造SEI NAL Unit代码如下:
sei->nal_unit_header.nal_unit_type = H264_NAL_SEI;
err = ff_cbs_insert_unit_content(ctx->cbc, au,
sei_pos, H264_NAL_SEI, sei);
if (err < 0) {
av_log(bsf, AV_LOG_ERROR, "Failed to insert SEI.\n");
goto fail;
}
payload = &sei->payload[sei->payload_count];
payload->payload_type = H264_SEI_TYPE_USER_DATA_UNREGISTERED;
udu = &payload->payload.user_data_unregistered;
for (i = j = 0; j < 32 && ctx->sei_user_data[i]; i++) {
int c, v;
c = ctx->sei_user_data[i];
if (c == '-') {
continue;
} else if (av_isxdigit(c)) {
c = av_tolower(c);
v = (c <= '9' ? c - '0' : c - 'a' + 10);
} else {
goto invalid_user_data;
}
if (i & 1)
udu->uuid_iso_iec_11578[j / 2] |= v;
else
udu->uuid_iso_iec_11578[j / 2] = v << 4;
++j;
}
if (j == 32 && ctx->sei_user_data[i] == '+') {
sei_udu_string = av_strdup(ctx->sei_user_data + i + 1);
if (!sei_udu_string) {
err = AVERROR(ENOMEM);
goto sei_fail;
}
udu->data = sei_udu_string;
udu->data_length = strlen(sei_udu_string);
payload->payload_size = 16 + udu->data_length;
}
代码完整解释了上文提到的SEI规范,其中"H264_SEI_TYPE_USER_DATA_UNREGISTERED"值为5,对应的即是未注册的用户信息。在解析"ffmpeg"工具输入过程中,将"+"号前面的字符串转换成二进制写入uuid,"+"后内容使用字符串写入payload。
x264
libx264支持多种SEI类型数据写入,常用的仍然是SEI_USER_DATA_UNREGISTERED,具体的写入函数x264_sei_version_write()位于libx264/encoder/set.c中。
int x264_sei_version_write( x264_t *h, bs_t *s )
{
static const uint8_t uuid[16] =
{
0xdc, 0x45, 0xe9, 0xbd, 0xe6, 0xd9, 0x48, 0xb7,
0x96, 0x2c, 0xd8, 0x20, 0xd9, 0x23, 0xee, 0xef
};
char *opts = x264_param2string( &h->param, 0 );
char *payload;
int length;
if( !opts )
return -1;
CHECKED_MALLOC( payload, 200 + strlen( opts ) );
memcpy( payload, uuid, 16 );
sprintf( payload+16, "x264 - core %d%s - H.264/MPEG-4 AVC codec - "
"Copy%s 2003-2018 - http://www.videolan.org/x264.html - options: %s",
X264_BUILD, X264_VERSION, HAVE_GPL?"left":"right", opts );
length = strlen(payload)+1;
x264_sei_write( s, (uint8_t *)payload, length, SEI_USER_DATA_UNREGISTERED );
x264_free( opts );
x264_free( payload );
return 0;
fail:
x264_free( opts );
return -1;
}
libx264提供的uuid和上文举例的uuid一致,payload中主要记录了相关参数和版权信息。以上函数完成了SEI参数的构造,下面的函数x264_sei_write完成了具体语法的写入:
void x264_sei_write( bs_t *s, uint8_t *payload, int payload_size, int payload_type )
{
int i;
bs_realign( s );
for( i = 0; i <= payload_type-255; i += 255 )
bs_write( s, 8, 255 );
bs_write( s, 8, payload_type-i );
for( i = 0; i <= payload_size-255; i += 255 )
bs_write( s, 8, 255 );
bs_write( s, 8, payload_size-i );
for( i = 0; i < payload_size; i++ )
bs_write( s, 8, payload[i] );
bs_rbsp_trailing( s );
bs_flush( s );
}
以上写入的代码逻辑和标准语法说明保持一致。
SEI解析
FFmpeg在读取和解码NAL unit,都有相同的逻辑处理SEI。
读取或者解码数据时,会调用下面函数进行码流的解码,其中buf包含具体的二进制流,buf_size是当前码流长度。函数内部会解析码流并实例出具体的NAL对象:
//Locate in libavcodec/h264dec.c
int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size)
如果NAL对象类型是SEI 时,将调用以下函数解码:
//Locate in libavcodec/h264_sei.c
int ff_h264_sei_decode(H264SEIContext *h, GetBitContext *gb,
const H264ParamSets *ps, void *logctx)
函数内部会判断SEI payload type进行不同的函数调用,如果是未注册的用户数据,则调用以下函数:
int decode_unregistered_user_data(H264SEIUnregistered *h, GetBitContext *gb,void *logctx, int size)
{
uint8_t *user_data;
int e, build, i;
if (size < 16 || size >= INT_MAX - 16)
return AVERROR_INVALIDDATA;
user_data = av_malloc(16 + size + 1);
if (!user_data)
return AVERROR(ENOMEM);
for (i = 0; i < size + 16; i++)
user_data[i] = get_bits(gb, 8);
user_data[i] = 0;
e = sscanf(user_data + 16, "x264 - core %d", &build);
if (e == 1 && build > 0)
h->x264_build = build;
if (e == 1 && build == 1 && !strncmp(user_data+16, "x264 - core 0000", 16))
h->x264_build = 67;
av_free(user_data);
return 0;
}
可以看到,根据SEI语法标准,在解析了SEI payload type和length后,对未注册用户数据的提取,跳过了uuid的分析,只尝试提取了x264的build信息。总体上,并未利用SEI_USER_DATA_UNREGISTERED传递过来的其他相关参数信息。
从解码器逻辑看,H264SEIUnregistered结构体只有一个x264_build属性,并未返回实质有效数据。上层业务如果需要提取SEI_USER_DATA_UNREGISTERED,仍然需要自己提取。提取逻辑,请参考下一小节(ffplay)。
ffplay
ffplay是一个简单、常用的FFmpeg接口示例工具,常用于测试解码、播放效果。如果在ffplay中示例跑通SEI提取功能,可以很方便的移植到其他平台。
在ffplay中通过函数av_read_frame(ic, pkt)返回后,读取pkt->data可以快速拿到当前读到的NAL unit。从data数据中取出NAL unit type,如果是SEI且是用户未注册数据类型(payload type值为5),则可以参考SEI语法继续读取UUID和其后传递的字符串。
总结
本文主要对H.264码流中涉及用户未注册数据的SEI进行了分析。总体而言,SEI只是视频标准里面很小的一部分,但在应用过程中,比如直播问答项目中SEI承载的信息,就极大提升了直播观看和答题操作的整体用户体验。所以说,从SEI的例子中,我们就会发现,视频标准里面还有很多金矿等待着大家的挖掘,这就是多媒体技术的魅力,也是金山云努力的方向。
相关报道:
- 金山云披露2017年财报数据 AI企业级商用带动高速增长2018-03-21
- FFmpeg代码导读系列:HEVC在RTMP中的扩展2018-02-01
- 金山云宣布融2亿美元 D轮资金累计达7.2亿美元2018-01-29
- 金山云全链路HTTPS安全方案开启用户体验新篇章2018-01-24
- 雷军旗下金山云融资3亿美元 全面降价布局多垂直领域2017-12-12